Compare commits
10 Commits
7376005868
...
23160be055
| Author | SHA1 | Date |
|---|---|---|
|
|
23160be055 | |
|
|
53347ed1fe | |
|
|
44f4f1c46f | |
|
|
b98e7cb42f | |
|
|
96f459c27d | |
|
|
8188e8861d | |
|
|
32746652aa | |
|
|
484b7ddb95 | |
|
|
754d70623c | |
|
|
9e2ccf5ac9 |
|
|
@ -0,0 +1,49 @@
|
|||
# Compound Engineering -- local config
|
||||
# Copy to .compound-engineering/config.local.yaml in your project root.
|
||||
# All settings are optional. Invalid values fall through to defaults.
|
||||
|
||||
# --- Work delegation (Codex) ---
|
||||
|
||||
# work_delegate: codex # codex | false (default: false)
|
||||
# work_delegate_consent: true # true | false (default: false)
|
||||
# work_delegate_sandbox: yolo # yolo | full-auto (default: yolo)
|
||||
# work_delegate_decision: auto # auto | ask (default: auto)
|
||||
# work_delegate_model: gpt-5.4 # any valid codex model (omit to use ~/.codex/config.toml default)
|
||||
# work_delegate_effort: high # minimal | low | medium | high | xhigh (omit to use ~/.codex/config.toml default)
|
||||
|
||||
# --- Product pulse ---
|
||||
# Settings written by /ce-product-pulse first-run interview. Re-run the skill with
|
||||
# argument `setup` or `reconfigure` to edit interactively.
|
||||
|
||||
# pulse_product_name: "Spiral" # used in report titles (no default)
|
||||
# pulse_lookback_default: 24h # 1h | 24h | 7d | 30d (default: 24h)
|
||||
# pulse_primary_event: "session_started" # the event that means "user showed up"
|
||||
# pulse_value_event: "task_completed" # the event that means "user got value"
|
||||
# pulse_completion_events: "onboarded,first_purchase" # comma-separated, 0-3 events
|
||||
# pulse_quality_scoring: false # true | false (default: false; AI products only)
|
||||
# pulse_quality_dimension: "answer accuracy" # dimension scored 1-5 when pulse_quality_scoring is true
|
||||
# pulse_analytics_source: posthog # posthog | mixpanel | custom (no default)
|
||||
# pulse_tracing_source: sentry # sentry | datadog | custom (no default)
|
||||
# pulse_payments_source: stripe # stripe | custom (no default)
|
||||
# pulse_db_enabled: false # true | false (default: false; read-only DB if true)
|
||||
# pulse_metric_sources: "retention_d7=posthog,nps=delighted" # strategy-metric -> source overrides; comma-separated 'metric=source' pairs; unlisted metrics fall back to pulse_analytics_source
|
||||
# pulse_pending_metrics: "retention_d7,nps" # comma-separated strategy metrics awaiting instrumentation; render as 'no data'
|
||||
# pulse_excluded_metrics: "north_star" # comma-separated strategy metrics intentionally not in pulse
|
||||
|
||||
# --- Output format ---
|
||||
# Per-skill output format default. Selects the exclusive format the artifact
|
||||
# is written in: `md` produces a markdown file, `html` produces a single
|
||||
# self-contained HTML file. The two are mutually exclusive -- there is no
|
||||
# sibling artifact. See DESIGN.md or your agent instructions to influence
|
||||
# HTML styling. CLI arguments override these defaults; pipeline contexts
|
||||
# (e.g., LFG, disable-model-invocation) always force `md` regardless.
|
||||
|
||||
# plan_output: html # md | html (default: md)
|
||||
# brainstorm_output: html # md | html (default: md)
|
||||
# ideate_output: md # md | html (default: html -- ideation docs are human-facing, so HTML is the default; set md to opt out)
|
||||
|
||||
# --- ce-promote ---
|
||||
# Written automatically when you decline the Spiral setup offer in /ce-promote.
|
||||
# Suppresses that one-time setup nudge in this project. Remove the key to re-enable.
|
||||
|
||||
# ce_promote_spiral_optout: true # true | (absent) (default: absent -- offer once)
|
||||
|
|
@ -44,6 +44,8 @@ src/agentkit/server/static/
|
|||
|
||||
# Env
|
||||
.env
|
||||
.env.dev
|
||||
.env.local
|
||||
|
||||
# Runtime data (auth DB, conversation DB, etc.)
|
||||
data/
|
||||
|
|
@ -52,3 +54,10 @@ data/
|
|||
.agents/
|
||||
.trae/
|
||||
.aider*
|
||||
|
||||
# Knowledge graph tooling (local-only, generated index)
|
||||
.understand-anything/
|
||||
|
||||
# Local temp files
|
||||
tmp_*.html
|
||||
/delete_old_cluster.sh
|
||||
|
|
|
|||
|
|
@ -1,315 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Fischer AgentKit - Knowledge Graph Dashboard</title>
|
||||
<script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0f172a; color: #e2e8f0; overflow: hidden; height: 100vh; }
|
||||
.header { background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%); padding: 12px 24px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #334155; }
|
||||
.header h1 { font-size: 18px; font-weight: 600; background: linear-gradient(135deg, #60a5fa, #a78bfa); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
||||
.stats { display: flex; gap: 16px; font-size: 13px; color: #94a3b8; }
|
||||
.stats span { padding: 4px 10px; background: #1e293b; border-radius: 6px; border: 1px solid #334155; }
|
||||
.main { display: flex; height: calc(100vh - 52px); }
|
||||
.sidebar { width: 340px; background: #1e293b; border-right: 1px solid #334155; display: flex; flex-direction: column; overflow: hidden; }
|
||||
.search-box { padding: 12px; border-bottom: 1px solid #334155; }
|
||||
.search-box input { width: 100%; padding: 8px 12px; background: #0f172a; border: 1px solid #475569; border-radius: 8px; color: #e2e8f0; font-size: 14px; outline: none; }
|
||||
.search-box input:focus { border-color: #60a5fa; }
|
||||
.tabs { display: flex; border-bottom: 1px solid #334155; }
|
||||
.tab { flex: 1; padding: 8px; text-align: center; font-size: 13px; cursor: pointer; color: #94a3b8; border-bottom: 2px solid transparent; transition: all 0.2s; }
|
||||
.tab.active { color: #60a5fa; border-bottom-color: #60a5fa; }
|
||||
.tab:hover { color: #e2e8f0; }
|
||||
.content { flex: 1; overflow-y: auto; padding: 8px; }
|
||||
.content::-webkit-scrollbar { width: 6px; }
|
||||
.content::-webkit-scrollbar-thumb { background: #475569; border-radius: 3px; }
|
||||
.node-item { padding: 8px 12px; border-radius: 6px; cursor: pointer; margin-bottom: 4px; transition: background 0.15s; border: 1px solid transparent; }
|
||||
.node-item:hover { background: #334155; border-color: #475569; }
|
||||
.node-item.selected { background: #1e3a5f; border-color: #60a5fa; }
|
||||
.node-item .name { font-size: 13px; font-weight: 500; color: #e2e8f0; }
|
||||
.node-item .meta { font-size: 11px; color: #64748b; margin-top: 2px; }
|
||||
.badge { display: inline-block; padding: 1px 6px; border-radius: 4px; font-size: 10px; font-weight: 600; margin-left: 6px; }
|
||||
.badge.file { background: #1e3a5f; color: #60a5fa; }
|
||||
.badge.class { background: #3b1f5e; color: #c084fc; }
|
||||
.badge.function { background: #1a3d2e; color: #4ade80; }
|
||||
.badge.api { background: #7c2d12; color: #fb923c; }
|
||||
.badge.service { background: #1e3a5f; color: #60a5fa; }
|
||||
.badge.data { background: #3b1f5e; color: #c084fc; }
|
||||
.badge.utility { background: #1a3d2e; color: #4ade80; }
|
||||
.graph-container { flex: 1; position: relative; }
|
||||
#graph { width: 100%; height: 100%; }
|
||||
.detail-panel { position: absolute; right: 16px; top: 16px; width: 320px; background: rgba(30,41,59,0.95); border: 1px solid #475569; border-radius: 12px; padding: 16px; display: none; backdrop-filter: blur(12px); max-height: calc(100vh - 100px); overflow-y: auto; }
|
||||
.detail-panel.show { display: block; }
|
||||
.detail-panel h3 { font-size: 15px; color: #60a5fa; margin-bottom: 8px; }
|
||||
.detail-panel .summary { font-size: 13px; color: #cbd5e1; line-height: 1.5; margin-bottom: 12px; }
|
||||
.detail-panel .field { font-size: 12px; color: #94a3b8; margin-bottom: 6px; }
|
||||
.detail-panel .field strong { color: #e2e8f0; }
|
||||
.detail-panel .edges-list { margin-top: 8px; }
|
||||
.detail-panel .edge-item { font-size: 12px; padding: 4px 8px; background: #0f172a; border-radius: 4px; margin-bottom: 3px; cursor: pointer; }
|
||||
.detail-panel .edge-item:hover { background: #334155; }
|
||||
.tour-panel { padding: 8px; }
|
||||
.tour-card { background: #0f172a; border: 1px solid #334155; border-radius: 8px; padding: 12px; margin-bottom: 8px; cursor: pointer; transition: border-color 0.2s; }
|
||||
.tour-card:hover { border-color: #60a5fa; }
|
||||
.tour-card h4 { font-size: 13px; color: #e2e8f0; margin-bottom: 4px; }
|
||||
.tour-card p { font-size: 11px; color: #64748b; }
|
||||
.tour-steps { margin-top: 8px; }
|
||||
.tour-step { display: flex; align-items: center; gap: 8px; padding: 6px 8px; font-size: 12px; color: #94a3b8; border-radius: 4px; cursor: pointer; }
|
||||
.tour-step:hover { background: #334155; color: #e2e8f0; }
|
||||
.tour-step .num { width: 20px; height: 20px; background: #334155; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 10px; color: #60a5fa; flex-shrink: 0; }
|
||||
.layer-filter { display: flex; flex-wrap: wrap; gap: 4px; padding: 8px 12px; border-bottom: 1px solid #334155; }
|
||||
.layer-btn { padding: 3px 8px; border-radius: 4px; font-size: 11px; cursor: pointer; border: 1px solid #475569; background: transparent; color: #94a3b8; transition: all 0.15s; }
|
||||
.layer-btn.active { background: #334155; color: #e2e8f0; border-color: #60a5fa; }
|
||||
.close-btn { position: absolute; top: 8px; right: 8px; background: none; border: none; color: #94a3b8; cursor: pointer; font-size: 16px; }
|
||||
.close-btn:hover { color: #e2e8f0; }
|
||||
.loading { display: flex; align-items: center; justify-content: center; height: 100%; font-size: 16px; color: #60a5fa; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>Fischer AgentKit Knowledge Graph</h1>
|
||||
<div class="stats" id="stats"></div>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class="sidebar">
|
||||
<div class="search-box">
|
||||
<input type="text" id="search" placeholder="Search nodes..." />
|
||||
</div>
|
||||
<div class="layer-filter" id="layerFilter"></div>
|
||||
<div class="tabs">
|
||||
<div class="tab active" data-tab="nodes">Nodes</div>
|
||||
<div class="tab" data-tab="tours">Tours</div>
|
||||
</div>
|
||||
<div class="content" id="nodeList"></div>
|
||||
<div class="content" id="tourList" style="display:none"></div>
|
||||
</div>
|
||||
<div class="graph-container">
|
||||
<div id="graph"></div>
|
||||
<div class="detail-panel" id="detailPanel">
|
||||
<button class="close-btn" onclick="document.getElementById('detailPanel').classList.remove('show')">×</button>
|
||||
<div id="detailContent"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let graphData = null;
|
||||
let network = null;
|
||||
let nodesDataset = null;
|
||||
let edgesDataset = null;
|
||||
let activeLayers = new Set(['api', 'service', 'data', 'utility', 'unknown']);
|
||||
let selectedNodeId = null;
|
||||
|
||||
const LAYER_COLORS = {
|
||||
api: { bg: '#fb923c', border: '#ea580c', highlight: '#fdba74' },
|
||||
service: { bg: '#60a5fa', border: '#2563eb', highlight: '#93c5fd' },
|
||||
data: { bg: '#c084fc', border: '#9333ea', highlight: '#d8b4fe' },
|
||||
utility: { bg: '#4ade80', border: '#16a34a', highlight: '#86efac' },
|
||||
unknown: { bg: '#94a3b8', border: '#64748b', highlight: '#cbd5e1' }
|
||||
};
|
||||
|
||||
const TYPE_SHAPES = { file: 'box', class: 'diamond', function: 'dot' };
|
||||
|
||||
async function loadGraph() {
|
||||
try {
|
||||
const resp = await fetch('knowledge-graph.json');
|
||||
graphData = await resp.json();
|
||||
initDashboard();
|
||||
} catch(e) {
|
||||
document.getElementById('graph').innerHTML = '<div class="loading">Failed to load knowledge-graph.json</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function initDashboard() {
|
||||
const { nodes, edges, tours, project } = graphData;
|
||||
document.getElementById('stats').innerHTML = `
|
||||
<span>${nodes.length} Nodes</span>
|
||||
<span>${edges.length} Edges</span>
|
||||
<span>${tours.length} Tours</span>
|
||||
<span>${project.languages.join(', ')}</span>
|
||||
`;
|
||||
renderLayerFilter(nodes);
|
||||
renderNodeList(nodes);
|
||||
renderTourList(tours);
|
||||
initGraph(nodes, edges);
|
||||
initSearch(nodes);
|
||||
initTabs();
|
||||
}
|
||||
|
||||
function renderLayerFilter(nodes) {
|
||||
const layers = [...new Set(nodes.map(n => n.layer).filter(Boolean))];
|
||||
const container = document.getElementById('layerFilter');
|
||||
container.innerHTML = layers.map(l =>
|
||||
`<button class="layer-btn active" data-layer="${l}">${l}</button>`
|
||||
).join('');
|
||||
container.querySelectorAll('.layer-btn').forEach(btn => {
|
||||
btn.onclick = () => {
|
||||
const layer = btn.dataset.layer;
|
||||
if (activeLayers.has(layer)) { activeLayers.delete(layer); btn.classList.remove('active'); }
|
||||
else { activeLayers.add(layer); btn.classList.add('active'); }
|
||||
filterGraph();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function renderNodeList(nodes) {
|
||||
const container = document.getElementById('nodeList');
|
||||
const filtered = nodes.filter(n => activeLayers.has(n.layer));
|
||||
container.innerHTML = filtered.slice(0, 200).map(n => `
|
||||
<div class="node-item" data-id="${n.id}">
|
||||
<div class="name">${n.name} <span class="badge ${n.type}">${n.type}</span> <span class="badge ${n.layer}">${n.layer}</span></div>
|
||||
<div class="meta">${n.filePath || ''}</div>
|
||||
</div>
|
||||
`).join('');
|
||||
container.querySelectorAll('.node-item').forEach(el => {
|
||||
el.onclick = () => focusNode(el.dataset.id);
|
||||
});
|
||||
}
|
||||
|
||||
function renderTourList(tours) {
|
||||
const container = document.getElementById('tourList');
|
||||
container.innerHTML = tours.map((t, i) => `
|
||||
<div class="tour-card" data-tour="${i}">
|
||||
<h4>${t.name}</h4>
|
||||
<p>${t.description}</p>
|
||||
<div class="tour-steps">
|
||||
${t.steps.map((s, j) => `
|
||||
<div class="tour-step" data-node="${s.nodeId}">
|
||||
<span class="num">${j+1}</span>
|
||||
<span>${s.why || s.nodeId}</span>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
container.querySelectorAll('.tour-step').forEach(el => {
|
||||
el.onclick = (e) => { e.stopPropagation(); focusNode(el.dataset.node); };
|
||||
});
|
||||
}
|
||||
|
||||
function initGraph(nodes, edges) {
|
||||
const visNodes = nodes.filter(n => activeLayers.has(n.layer)).map(n => {
|
||||
const colors = LAYER_COLORS[n.layer] || LAYER_COLORS.unknown;
|
||||
return {
|
||||
id: n.id,
|
||||
label: n.name,
|
||||
shape: TYPE_SHAPES[n.type] || 'dot',
|
||||
color: colors,
|
||||
font: { color: '#e2e8f0', size: n.type === 'file' ? 12 : 10 },
|
||||
size: n.type === 'file' ? 20 : n.type === 'class' ? 15 : 8,
|
||||
title: `${n.name}\n${n.summary || ''}\n[${n.layer}]`,
|
||||
...n
|
||||
};
|
||||
});
|
||||
|
||||
const nodeIds = new Set(visNodes.map(n => n.id));
|
||||
const visEdges = edges.filter(e => nodeIds.has(e.source) && nodeIds.has(e.target)).map((e, i) => ({
|
||||
id: e.id || `edge-${i}`,
|
||||
from: e.source,
|
||||
to: e.target,
|
||||
arrows: e.type === 'contains' ? '' : 'to',
|
||||
color: { color: '#334155', highlight: '#60a5fa', hover: '#475569' },
|
||||
width: 0.5,
|
||||
dashes: e.type === 'imports',
|
||||
title: e.type
|
||||
}));
|
||||
|
||||
nodesDataset = new vis.DataSet(visNodes);
|
||||
edgesDataset = new vis.DataSet(visEdges);
|
||||
|
||||
const container = document.getElementById('graph');
|
||||
const data = { nodes: nodesDataset, edges: edgesDataset };
|
||||
const options = {
|
||||
physics: { barnesHut: { gravitationalConstant: -3000, centralGravity: 0.3, springLength: 80, springConstant: 0.04 }, stabilization: { iterations: 100 } },
|
||||
interaction: { hover: true, tooltipDelay: 200, navigationButtons: true, keyboard: true },
|
||||
layout: { improvedLayout: true }
|
||||
};
|
||||
network = new vis.Network(container, data, options);
|
||||
network.on('click', params => {
|
||||
if (params.nodes.length > 0) focusNode(params.nodes[0]);
|
||||
});
|
||||
}
|
||||
|
||||
function filterGraph() {
|
||||
const nodes = graphData.nodes.filter(n => activeLayers.has(n.layer));
|
||||
renderNodeList(nodes);
|
||||
if (nodesDataset) {
|
||||
const nodeIds = new Set(nodes.map(n => n.id));
|
||||
const edges = graphData.edges.filter(e => nodeIds.has(e.source) && nodeIds.has(e.target));
|
||||
nodesDataset.clear();
|
||||
nodesDataset.add(nodes.map(n => {
|
||||
const colors = LAYER_COLORS[n.layer] || LAYER_COLORS.unknown;
|
||||
return { id: n.id, label: n.name, shape: TYPE_SHAPES[n.type] || 'dot', color: colors, font: { color: '#e2e8f0', size: n.type === 'file' ? 12 : 10 }, size: n.type === 'file' ? 20 : n.type === 'class' ? 15 : 8, title: `${n.name}\n${n.summary || ''}\n[${n.layer}]`, ...n };
|
||||
}));
|
||||
edgesDataset.clear();
|
||||
edgesDataset.add(edges.map((e, i) => ({ id: e.id || `edge-${i}`, from: e.source, to: e.target, arrows: e.type === 'contains' ? '' : 'to', color: { color: '#334155', highlight: '#60a5fa', hover: '#475569' }, width: 0.5, dashes: e.type === 'imports', title: e.type })));
|
||||
}
|
||||
}
|
||||
|
||||
function focusNode(nodeId) {
|
||||
selectedNodeId = nodeId;
|
||||
const node = graphData.nodes.find(n => n.id === nodeId);
|
||||
if (!node) return;
|
||||
|
||||
document.querySelectorAll('.node-item').forEach(el => el.classList.remove('selected'));
|
||||
const el = document.querySelector(`.node-item[data-id="${nodeId}"]`);
|
||||
if (el) { el.classList.add('selected'); el.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); }
|
||||
|
||||
if (network) {
|
||||
network.focus(nodeId, { scale: 1.5, animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
|
||||
network.selectNodes([nodeId]);
|
||||
}
|
||||
|
||||
const inEdges = graphData.edges.filter(e => e.target === nodeId);
|
||||
const outEdges = graphData.edges.filter(e => e.source === nodeId);
|
||||
|
||||
const panel = document.getElementById('detailPanel');
|
||||
const content = document.getElementById('detailContent');
|
||||
content.innerHTML = `
|
||||
<h3>${node.name}</h3>
|
||||
<div class="summary">${node.summary || 'No summary'}</div>
|
||||
<div class="field"><strong>Type:</strong> ${node.type}</div>
|
||||
<div class="field"><strong>Layer:</strong> ${node.layer}</div>
|
||||
<div class="field"><strong>Path:</strong> ${node.filePath || '-'}</div>
|
||||
<div class="field"><strong>Complexity:</strong> ${node.complexity || '-'}</div>
|
||||
${node.tags ? `<div class="field"><strong>Tags:</strong> ${node.tags.join(', ')}</div>` : ''}
|
||||
${inEdges.length > 0 ? `<div class="edges-list"><strong>Incoming (${inEdges.length}):</strong>${inEdges.slice(0,10).map(e => `<div class="edge-item" onclick="focusNode('${e.source}')">${e.type} ← ${e.source.split(':').pop()}</div>`).join('')}</div>` : ''}
|
||||
${outEdges.length > 0 ? `<div class="edges-list"><strong>Outgoing (${outEdges.length}):</strong>${outEdges.slice(0,10).map(e => `<div class="edge-item" onclick="focusNode('${e.target}')">${e.type} → ${e.target.split(':').pop()}</div>`).join('')}</div>` : ''}
|
||||
`;
|
||||
panel.classList.add('show');
|
||||
}
|
||||
|
||||
function initSearch(nodes) {
|
||||
const input = document.getElementById('search');
|
||||
input.oninput = () => {
|
||||
const q = input.value.toLowerCase();
|
||||
const filtered = nodes.filter(n =>
|
||||
activeLayers.has(n.layer) &&
|
||||
(n.name.toLowerCase().includes(q) || (n.summary || '').toLowerCase().includes(q) || (n.filePath || '').toLowerCase().includes(q))
|
||||
);
|
||||
renderNodeList(filtered);
|
||||
if (q.length > 1 && network) {
|
||||
const matchIds = filtered.map(n => n.id);
|
||||
if (matchIds.length > 0 && matchIds.length < 50) {
|
||||
network.fit({ nodes: matchIds, animation: { duration: 500 } });
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function initTabs() {
|
||||
document.querySelectorAll('.tab').forEach(tab => {
|
||||
tab.onclick = () => {
|
||||
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
const target = tab.dataset.tab;
|
||||
document.getElementById('nodeList').style.display = target === 'nodes' ? 'block' : 'none';
|
||||
document.getElementById('tourList').style.display = target === 'tours' ? 'block' : 'none';
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
loadGraph();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,526 +0,0 @@
|
|||
{
|
||||
"configs/__init__.py": "c177bc3f2e94623f",
|
||||
"configs/geo_handlers.py": "4df68c7922568aab",
|
||||
"configs/geo_server.py": "189e38838336cad4",
|
||||
"configs/geo_tools.py": "3c442267d598dc1c",
|
||||
"docs/GEO-INTEGRATION-GUIDE.md": "bf3dfb02cf6846e1",
|
||||
"docs/brainstorms/2026-06-05-agentkit-architecture-gap-analysis-requirements.md": "eb46b87f5cdca0fa",
|
||||
"docs/brainstorms/2026-06-09-agentkit-capability-matrix/plan.md": "5829f1e545a08f0f",
|
||||
"docs/brainstorms/2026-06-09-agentkit-capability-matrix/requirements.md": "50e4f91f200089a8",
|
||||
"docs/brainstorms/2026-06-09-clawith-research-prompt.md": "82cbce39736c76dc",
|
||||
"docs/brainstorms/2026-06-12-frontend-productization-requirements.md": "41bb5034978e107c",
|
||||
"docs/brainstorms/2026-06-13-agentkit-platform-experience-upgrade-requirements.md": "97b00dd54e012bfd",
|
||||
"docs/brainstorms/2026-06-13-gui-productization-requirements.md": "501f89238bc6e504",
|
||||
"docs/brainstorms/2026-06-13-gui-redesign-requirements.md": "e703c3a1dff253c8",
|
||||
"docs/plans/2026-06-05-001-feat-agentkit-tdd-validation-plan.md": "cb31514b78616c90",
|
||||
"docs/plans/2026-06-05-002-design-agentkit-v2-architecture.md": "e64efa0d464352d6",
|
||||
"docs/plans/2026-06-05-003-feat-agentkit-v2-phase1-plan.md": "8bfaed59f42e58eb",
|
||||
"docs/plans/2026-06-05-004-geo-migration-mode-a.md": "552e179027bd15cc",
|
||||
"docs/plans/2026-06-05-005-refactor-agentkit-framework-hardening.md": "00cf6aa686fbe699",
|
||||
"docs/plans/2026-06-05-006-refactor-agentkit-v2-phase2-plan.md": "c58751ee3720d3f1",
|
||||
"docs/plans/2026-06-05-007-feat-agentkit-cli-deployment-plan.md": "c21d783c264ce1dc",
|
||||
"docs/plans/2026-06-06-008-feat-agentkit-phase3-upgrade-plan.md": "1ba1a31257aeb6ee",
|
||||
"docs/plans/2026-06-06-009-feat-agentkit-rag-optimization-plan.md": "596b971c39b92374",
|
||||
"docs/plans/2026-06-06-010-feat-agentkit-phase4-production-plan.md": "f5443a0b04f80eca",
|
||||
"docs/plans/2026-06-06-011-feat-agentkit-phase5-intelligence-plan.md": "35083bba9d52c3a4",
|
||||
"docs/plans/2026-06-07-012-feat-agentkit-phase6-toolkit-plan.md": "110616fdbf34b501",
|
||||
"docs/plans/2026-06-07-013-feat-agentkit-phase7-headroom-plan.md": "c4a730d9d1b1e3bc",
|
||||
"docs/plans/2026-06-07-014-fix-agentkit-p0-review-fixes-plan.md": "4b62eee79a38f8c0",
|
||||
"docs/plans/2026-06-07-015-feat-agentkit-phase8-chat-adaptive-plan.md": "d9730989b4e1056b",
|
||||
"docs/plans/2026-06-08-016-feat-agentkit-layered-memory-plan.md": "0fbb6ba2badbd690",
|
||||
"docs/plans/2026-06-09-017-feat-agentkit-multi-agent-marketplace-plan.md": "125227b9fab2f7cf",
|
||||
"docs/plans/2026-06-10-018-fix-agentkit-p2-hardening-plan.md": "fb2b2de97bc5d108",
|
||||
"docs/plans/2026-06-10-019-feat-agentkit-deferred-improvements-plan.md": "d59405e0d5660cbc",
|
||||
"docs/plans/2026-06-12-020-feat-pipeline-adversarial-loop-plan.md": "8d857040371b14c9",
|
||||
"docs/plans/2026-06-12-021-feat-chat-response-speed-optimization-plan.md": "9a5e58432a3b9bbd",
|
||||
"docs/plans/2026-06-12-022-feat-agentkit-phase9-integrated-next-stage-plan.md": "8a18b30c7a7d4976",
|
||||
"docs/plans/2026-06-12-023-feat-frontend-productization-plan.md": "c17f31ebce588103",
|
||||
"docs/plans/2026-06-13-001-feat-gui-productization-plan.md": "924d3081d6a4c328",
|
||||
"docs/plans/2026-06-13-001-refactor-gui-redesign-plan.md": "5ec64af7d790295e",
|
||||
"docs/plans/2026-06-13-003-feat-platform-experience-upgrade-plan.md": "354b625271f06f5e",
|
||||
"docs/plans/2026-06-13-004-feat-tauri-desktop-client-plan.md": "c9afebfb3dae8a90",
|
||||
"docs/plans/2026-06-14-001-feat-p0-production-hardening-plan.md": "29737af17bfd9ade",
|
||||
"docs/plans/2026-06-14-002-u1-llm-cache-architecture.md": "71feabd02ad95169",
|
||||
"docs/plans/2026-06-14-003-u2-llm-cache-integration.md": "853bfdf22312dbae",
|
||||
"docs/plans/2026-06-14-004-u3-semantic-router.md": "6c765cbc5140be53",
|
||||
"src/agentkit/__init__.py": "8f3077a792be01bf",
|
||||
"src/agentkit/__main__.py": "a79cda859a4ff1e4",
|
||||
"src/agentkit/bus/__init__.py": "46dddb662dbc4455",
|
||||
"src/agentkit/bus/interface.py": "4172d55fa0e96410",
|
||||
"src/agentkit/bus/memory_bus.py": "993b110d1aa1bf8b",
|
||||
"src/agentkit/bus/message.py": "21093d396668686e",
|
||||
"src/agentkit/bus/protocol.py": "ceb9511a718981ec",
|
||||
"src/agentkit/bus/redis_bus.py": "b18f9d14abb106a4",
|
||||
"src/agentkit/chat/__init__.py": "d41d8cd98f00b204",
|
||||
"src/agentkit/chat/semantic_router.py": "ce5293262dc5fc6d",
|
||||
"src/agentkit/chat/skill_routing.py": "4edfe82920e62b29",
|
||||
"src/agentkit/cli/__init__.py": "6be0f88bfb1933cd",
|
||||
"src/agentkit/cli/chat.py": "555a58858d7ac531",
|
||||
"src/agentkit/cli/init.py": "c941e7e24b524414",
|
||||
"src/agentkit/cli/main.py": "2f67646566ddbba2",
|
||||
"src/agentkit/cli/onboarding.py": "c62b4f1b5508b05f",
|
||||
"src/agentkit/cli/pair.py": "b796381a116076a3",
|
||||
"src/agentkit/cli/skill.py": "513a7848cf995b79",
|
||||
"src/agentkit/cli/task.py": "19ba46de4dfe86ee",
|
||||
"src/agentkit/cli/templates.py": "3e3ea04125ac45dd",
|
||||
"src/agentkit/cli/usage.py": "988272ab7ffb34ff",
|
||||
"src/agentkit/core/__init__.py": "6e1420ad43fe4f94",
|
||||
"src/agentkit/core/agent_pool.py": "38c413cc11f0be6c",
|
||||
"src/agentkit/core/base.py": "330fbf17f4dfa01b",
|
||||
"src/agentkit/core/compressor.py": "eae7a723d1b55bc3",
|
||||
"src/agentkit/core/config_driven.py": "7592e9094dcbfa8b",
|
||||
"src/agentkit/core/dispatcher.py": "a032ac64cc6d88e2",
|
||||
"src/agentkit/core/exceptions.py": "a2e3376e0b06c6df",
|
||||
"src/agentkit/core/goal_planner.py": "85a82ad127be83df",
|
||||
"src/agentkit/core/headroom_compressor.py": "79691d95f00a9f2c",
|
||||
"src/agentkit/core/logging.py": "4ca908eac76f4487",
|
||||
"src/agentkit/core/orchestrator.py": "0b73a7612bf0d5fb",
|
||||
"src/agentkit/core/plan_checker.py": "da4b29c79546f1ef",
|
||||
"src/agentkit/core/plan_exec_engine.py": "5e78dab734dbd7a5",
|
||||
"src/agentkit/core/plan_executor.py": "4e145a4903d2c159",
|
||||
"src/agentkit/core/plan_schema.py": "5516e66e2a5241a6",
|
||||
"src/agentkit/core/protocol.py": "6cfa0bfb01ee29f3",
|
||||
"src/agentkit/core/react.py": "77124fe27b73d5fa",
|
||||
"src/agentkit/core/reflexion.py": "23f90a739bfdb96b",
|
||||
"src/agentkit/core/registry.py": "537905f014c67bc9",
|
||||
"src/agentkit/core/rewoo.py": "3ad60b5f4015b434",
|
||||
"src/agentkit/core/shared_workspace.py": "a847f5a879c5c551",
|
||||
"src/agentkit/core/standalone.py": "aa0bf44a1b1649a6",
|
||||
"src/agentkit/core/trace.py": "b6a4f8cebca7d594",
|
||||
"src/agentkit/evaluation/__init__.py": "a330342ce4ca36e5",
|
||||
"src/agentkit/evaluation/ragas_evaluator.py": "8f8d3f013d02e8b4",
|
||||
"src/agentkit/evolution/__init__.py": "723b5130fb48b695",
|
||||
"src/agentkit/evolution/ab_tester.py": "7841dff79e521e81",
|
||||
"src/agentkit/evolution/evolution_store.py": "af3444081d9daa98",
|
||||
"src/agentkit/evolution/experience_schema.py": "d2c096f18e1699ef",
|
||||
"src/agentkit/evolution/experience_store.py": "d58a3d12c5bc9b2e",
|
||||
"src/agentkit/evolution/fitness.py": "cc3a200bc1a45f8c",
|
||||
"src/agentkit/evolution/genetic.py": "f688039da416f456",
|
||||
"src/agentkit/evolution/lifecycle.py": "ec1fe00da015f086",
|
||||
"src/agentkit/evolution/llm_reflector.py": "11a57b403186159b",
|
||||
"src/agentkit/evolution/models.py": "f4431b403fb631be",
|
||||
"src/agentkit/evolution/path_optimizer.py": "e14acc297c384e1f",
|
||||
"src/agentkit/evolution/pg_store.py": "7d73520b6aaf6520",
|
||||
"src/agentkit/evolution/pitfall_detector.py": "b20426d9c8ad32f0",
|
||||
"src/agentkit/evolution/prompt_optimizer.py": "55d6803ff662373f",
|
||||
"src/agentkit/evolution/reflector.py": "3bd2e26da20cf83f",
|
||||
"src/agentkit/evolution/strategy_tuner.py": "bda557828a67e072",
|
||||
"src/agentkit/llm/__init__.py": "82c7e6de6d66bb43",
|
||||
"src/agentkit/llm/cache.py": "12e3ca6b88970d1b",
|
||||
"src/agentkit/llm/cache_key.py": "647aba5b414ee210",
|
||||
"src/agentkit/llm/config.py": "c6248208496fa3aa",
|
||||
"src/agentkit/llm/gateway.py": "7725c78f65c86b22",
|
||||
"src/agentkit/llm/protocol.py": "e37a2f29cc468686",
|
||||
"src/agentkit/llm/providers/__init__.py": "782e0bfe4705d0a9",
|
||||
"src/agentkit/llm/providers/anthropic.py": "bb56d751393249f0",
|
||||
"src/agentkit/llm/providers/doubao.py": "15a834e0808a9243",
|
||||
"src/agentkit/llm/providers/gemini.py": "d8531fc268d702d2",
|
||||
"src/agentkit/llm/providers/openai.py": "33a7e10d8fc09e47",
|
||||
"src/agentkit/llm/providers/tracker.py": "949dab590baf4768",
|
||||
"src/agentkit/llm/providers/usage_store.py": "d7e8bf9cd92e3f70",
|
||||
"src/agentkit/llm/providers/wenxin.py": "3748b70a13c1463f",
|
||||
"src/agentkit/llm/providers/yuanbao.py": "9fcc4b6ad8ac31b4",
|
||||
"src/agentkit/llm/retry.py": "2aa47e585f77dd50",
|
||||
"src/agentkit/marketplace/__init__.py": "7eba735388883e2d",
|
||||
"src/agentkit/marketplace/auction.py": "593df1eb74b19149",
|
||||
"src/agentkit/marketplace/wealth.py": "49b6d56a6dc938b9",
|
||||
"src/agentkit/mcp/__init__.py": "c043d1a081979781",
|
||||
"src/agentkit/mcp/client.py": "287662e5fa494e0f",
|
||||
"src/agentkit/mcp/manager.py": "0652af492b6d0d02",
|
||||
"src/agentkit/mcp/server.py": "b2689b6ff79e98ec",
|
||||
"src/agentkit/mcp/transport.py": "6c509f28b30eeb93",
|
||||
"src/agentkit/memory/__init__.py": "899e6e94f549d9ee",
|
||||
"src/agentkit/memory/adapters/__init__.py": "d80a08cc9de5c4e5",
|
||||
"src/agentkit/memory/adapters/base.py": "045ccec94443abb2",
|
||||
"src/agentkit/memory/adapters/confluence.py": "13e76eb173bfaf3d",
|
||||
"src/agentkit/memory/adapters/feishu.py": "011d187d101c942b",
|
||||
"src/agentkit/memory/adapters/generic_http.py": "ecd08ccaf23fb6a9",
|
||||
"src/agentkit/memory/base.py": "da502f486f860246",
|
||||
"src/agentkit/memory/chunking.py": "7cdd1dcb43eaaecd",
|
||||
"src/agentkit/memory/contextual_retrieval.py": "5c192a0c86b7fc45",
|
||||
"src/agentkit/memory/document_loader.py": "b9af5438034e1450",
|
||||
"src/agentkit/memory/embedder.py": "1cf207b86c87fb9f",
|
||||
"src/agentkit/memory/episodic.py": "d9eadf7068d02985",
|
||||
"src/agentkit/memory/http_rag.py": "c115b197f512896d",
|
||||
"src/agentkit/memory/knowledge_base.py": "594e0601119ba926",
|
||||
"src/agentkit/memory/local_rag.py": "35c473b15ce6ba0f",
|
||||
"src/agentkit/memory/models.py": "1ed936d411b508b2",
|
||||
"src/agentkit/memory/multi_source_retriever.py": "a528a4d316d9b1a7",
|
||||
"src/agentkit/memory/profile.py": "8d1144e61dc41ab0",
|
||||
"src/agentkit/memory/query_transformer.py": "4ed2930eba6ffa32",
|
||||
"src/agentkit/memory/rag_loop.py": "9f22171fd1ad1e4c",
|
||||
"src/agentkit/memory/relevance_scorer.py": "bd5bde3493c1d88f",
|
||||
"src/agentkit/memory/retriever.py": "3d124234ea5f6bc2",
|
||||
"src/agentkit/memory/semantic.py": "d8330a3242202690",
|
||||
"src/agentkit/memory/working.py": "2c23cc7e7311fca8",
|
||||
"src/agentkit/orchestrator/__init__.py": "c3fc598ee58f6f8b",
|
||||
"src/agentkit/orchestrator/compensation.py": "439752d48824ac1a",
|
||||
"src/agentkit/orchestrator/dynamic_pipeline.py": "a71d105fd793c873",
|
||||
"src/agentkit/orchestrator/handoff.py": "088b49d79a7d8ce5",
|
||||
"src/agentkit/orchestrator/pipeline_engine.py": "2aa54170ff33511f",
|
||||
"src/agentkit/orchestrator/pipeline_loader.py": "367bdc2921b95136",
|
||||
"src/agentkit/orchestrator/pipeline_models.py": "6a565353eca205aa",
|
||||
"src/agentkit/orchestrator/pipeline_schema.py": "7002a77b7bd837b6",
|
||||
"src/agentkit/orchestrator/pipeline_state.py": "61e7b05b8ff7d7c3",
|
||||
"src/agentkit/orchestrator/reflection.py": "ffb779fa4ee52e4d",
|
||||
"src/agentkit/orchestrator/retry.py": "d3664f5702da0891",
|
||||
"src/agentkit/orchestrator/workflow_schema.py": "304c14f9107e18d3",
|
||||
"src/agentkit/org/__init__.py": "cfa58426f7f486f6",
|
||||
"src/agentkit/org/context.py": "2f2e35245c8d460d",
|
||||
"src/agentkit/org/discovery.py": "ceb833baf51b62ff",
|
||||
"src/agentkit/prompts/__init__.py": "3edd02e1768b0daa",
|
||||
"src/agentkit/prompts/section.py": "f1d167a2f0abebf5",
|
||||
"src/agentkit/prompts/template.py": "717673ca624bbe25",
|
||||
"src/agentkit/quality/__init__.py": "1cd7368784872d72",
|
||||
"src/agentkit/quality/alignment.py": "f252282b05baa04d",
|
||||
"src/agentkit/quality/cascade_detector.py": "62ceb3c9a6ea94f5",
|
||||
"src/agentkit/quality/cascade_state_store.py": "3e3afeabf789b676",
|
||||
"src/agentkit/quality/gate.py": "8f615904f6ef877e",
|
||||
"src/agentkit/quality/output.py": "9f596461e85908fd",
|
||||
"src/agentkit/router/__init__.py": "6b1080a95c77611c",
|
||||
"src/agentkit/router/intent.py": "0e8cb267be40071c",
|
||||
"src/agentkit/server/__init__.py": "e6ba7d89409ef8bc",
|
||||
"src/agentkit/server/app.py": "b300b1e0afdc3d3d",
|
||||
"src/agentkit/server/client.py": "d18b1a046e59ca55",
|
||||
"src/agentkit/server/client_config.py": "91e05d25245e90cf",
|
||||
"src/agentkit/server/config.py": "b45512d0b1b53073",
|
||||
"src/agentkit/server/frontend/components.d.ts": "5f476f813a2598b2",
|
||||
"src/agentkit/server/frontend/env.d.ts": "7e784bd82b37b057",
|
||||
"src/agentkit/server/frontend/package.json": "64e85b8f27c88709",
|
||||
"src/agentkit/server/frontend/src/App.vue": "cef09b6f2dae1f21",
|
||||
"src/agentkit/server/frontend/src/api/base.ts": "8f917711ede5bf8e",
|
||||
"src/agentkit/server/frontend/src/api/client.ts": "faf1c13362837642",
|
||||
"src/agentkit/server/frontend/src/api/evolution.ts": "a8369e1c5f2d53a1",
|
||||
"src/agentkit/server/frontend/src/api/kb.ts": "2d3ea26a96f0666d",
|
||||
"src/agentkit/server/frontend/src/api/settings.ts": "7c8523fcbd08e9cc",
|
||||
"src/agentkit/server/frontend/src/api/skills.ts": "83fc22d3ab317669",
|
||||
"src/agentkit/server/frontend/src/api/tauri.ts": "b4da451ccb551f44",
|
||||
"src/agentkit/server/frontend/src/api/terminal.ts": "c3566c3ef0db60e8",
|
||||
"src/agentkit/server/frontend/src/api/types.ts": "1b1ee4f23c38e580",
|
||||
"src/agentkit/server/frontend/src/api/workflow.ts": "e96945651f30a5e9",
|
||||
"src/agentkit/server/frontend/src/components/chat/ChatInput.vue": "afdebfb4570dcbee",
|
||||
"src/agentkit/server/frontend/src/components/chat/ChatMessage.vue": "06a2d6cb671e68a2",
|
||||
"src/agentkit/server/frontend/src/components/chat/ChatSidebar.vue": "cc6f7b8bbdcf449a",
|
||||
"src/agentkit/server/frontend/src/components/chat/ContextPill.vue": "330ed22357ce7616",
|
||||
"src/agentkit/server/frontend/src/components/chat/FilePreview.vue": "defd56ba76c588a0",
|
||||
"src/agentkit/server/frontend/src/components/chat/MentionDropdown.vue": "4c7d84ff26d7d899",
|
||||
"src/agentkit/server/frontend/src/components/chat/ToolCallCard.vue": "7081a83bd23f2414",
|
||||
"src/agentkit/server/frontend/src/components/chat/ToolCallIndicator.vue": "9431bf6cc2e76df8",
|
||||
"src/agentkit/server/frontend/src/components/code/CodeDiffViewer.vue": "6d668833fa45c2fd",
|
||||
"src/agentkit/server/frontend/src/components/code/FileTree.vue": "7ccd0d7f03c1c8c4",
|
||||
"src/agentkit/server/frontend/src/components/evolution/DashboardOverview.vue": "a1961bfd0b32228d",
|
||||
"src/agentkit/server/frontend/src/components/evolution/ExperiencePanel.vue": "21fcfabd936c32f5",
|
||||
"src/agentkit/server/frontend/src/components/evolution/ExperienceTimeline.vue": "d626e873a11e8c08",
|
||||
"src/agentkit/server/frontend/src/components/evolution/MetricsChart.vue": "06767d5c02e37823",
|
||||
"src/agentkit/server/frontend/src/components/evolution/MetricsPanel.vue": "66e873ddf0d0c542",
|
||||
"src/agentkit/server/frontend/src/components/evolution/OptimizationPanel.vue": "c0e148ad6648aab3",
|
||||
"src/agentkit/server/frontend/src/components/evolution/PathOptimizerPanel.vue": "bcc00116179846ba",
|
||||
"src/agentkit/server/frontend/src/components/evolution/PitfallPanel.vue": "f7c32f866c78f081",
|
||||
"src/agentkit/server/frontend/src/components/evolution/PitfallRoutePanel.vue": "141fa883a4f139f3",
|
||||
"src/agentkit/server/frontend/src/components/evolution/UsagePanel.vue": "3e7ae825a23b550e",
|
||||
"src/agentkit/server/frontend/src/components/kb/DocumentUpload.vue": "eaf08ca06861c8ee",
|
||||
"src/agentkit/server/frontend/src/components/kb/SearchTest.vue": "e813640e950768a2",
|
||||
"src/agentkit/server/frontend/src/components/kb/SourceConfig.vue": "9453a7d7466c190c",
|
||||
"src/agentkit/server/frontend/src/components/layout/AgentLayout.vue": "8302bdbff9f407fe",
|
||||
"src/agentkit/server/frontend/src/components/layout/AppLayout.vue": "0f3d4a85e031b87e",
|
||||
"src/agentkit/server/frontend/src/components/layout/IconNav.vue": "be857d42b74ed508",
|
||||
"src/agentkit/server/frontend/src/components/layout/QuadrantPanel.vue": "77329d3919b07f00",
|
||||
"src/agentkit/server/frontend/src/components/layout/SideNav.vue": "323ce7d138162303",
|
||||
"src/agentkit/server/frontend/src/components/layout/SplashScreen.vue": "ec6928eebd625c58",
|
||||
"src/agentkit/server/frontend/src/components/layout/SplitPane.vue": "75fc8951b277767b",
|
||||
"src/agentkit/server/frontend/src/components/layout/TitleBar.vue": "b6d128888ee306c8",
|
||||
"src/agentkit/server/frontend/src/components/layout/TopNav.vue": "2808cedccab1d945",
|
||||
"src/agentkit/server/frontend/src/components/skills/SkillCard.vue": "40c0fd217652f00d",
|
||||
"src/agentkit/server/frontend/src/components/skills/SkillDetail.vue": "9d1d4601560084cf",
|
||||
"src/agentkit/server/frontend/src/components/terminal/CommandHistory.vue": "faaf28480d749c4a",
|
||||
"src/agentkit/server/frontend/src/components/terminal/TerminalEmulator.vue": "609caa6d694356d5",
|
||||
"src/agentkit/server/frontend/src/components/workflow/ApprovalNode.vue": "f8f2461cec0a7315",
|
||||
"src/agentkit/server/frontend/src/components/workflow/ConditionNode.vue": "407959ec9eb47ee3",
|
||||
"src/agentkit/server/frontend/src/components/workflow/FlowCanvas.vue": "a8a03d2ee204d45e",
|
||||
"src/agentkit/server/frontend/src/components/workflow/NodePalette.vue": "95fb0648ce648331",
|
||||
"src/agentkit/server/frontend/src/components/workflow/ParallelNode.vue": "865dde4247960ec4",
|
||||
"src/agentkit/server/frontend/src/components/workflow/PropertyPanel.vue": "d43f36e67014da03",
|
||||
"src/agentkit/server/frontend/src/components/workflow/SkillNode.vue": "7ad0101f51d1c4aa",
|
||||
"src/agentkit/server/frontend/src/main.ts": "639546b71c6c8e64",
|
||||
"src/agentkit/server/frontend/src/router/index.ts": "29208a1d32a815ec",
|
||||
"src/agentkit/server/frontend/src/stores/capabilities.ts": "0176d1b76782ebc3",
|
||||
"src/agentkit/server/frontend/src/stores/chat.ts": "935a67e98204747b",
|
||||
"src/agentkit/server/frontend/src/stores/evolution.ts": "069b9010ed60a48e",
|
||||
"src/agentkit/server/frontend/src/stores/knowledge.ts": "99f61767dba1fe03",
|
||||
"src/agentkit/server/frontend/src/stores/settings.ts": "56d078006752617c",
|
||||
"src/agentkit/server/frontend/src/stores/skills.ts": "ae5050c1fb6ddd53",
|
||||
"src/agentkit/server/frontend/src/stores/terminal.ts": "cbccc94e9cc62778",
|
||||
"src/agentkit/server/frontend/src/stores/theme.ts": "58bd9259796d6c5b",
|
||||
"src/agentkit/server/frontend/src/stores/workflow.ts": "d41bf6daa2725f1e",
|
||||
"src/agentkit/server/frontend/src/styles/index.ts": "c5c64537137cdd6c",
|
||||
"src/agentkit/server/frontend/src/styles/responsive.css": "5555100df036dfde",
|
||||
"src/agentkit/server/frontend/src/styles/theme.ts": "9e10f0955fe24771",
|
||||
"src/agentkit/server/frontend/src/styles/tokens.css": "856befefe5094b09",
|
||||
"src/agentkit/server/frontend/src/styles/transitions.css": "5d761d0a4cc6fc7c",
|
||||
"src/agentkit/server/frontend/src/utils/echarts.ts": "ae4cffe3c5d35db6",
|
||||
"src/agentkit/server/frontend/src/utils/workflowSerializer.ts": "3b808fd32d78e280",
|
||||
"src/agentkit/server/frontend/src/views/ChatView.vue": "d19086d9f744decc",
|
||||
"src/agentkit/server/frontend/src/views/ComputerUseView.vue": "61c9868f1f7bf2aa",
|
||||
"src/agentkit/server/frontend/src/views/EvolutionView.vue": "508f4b284dc6d96d",
|
||||
"src/agentkit/server/frontend/src/views/KnowledgeBaseView.vue": "90a70c08c1ad2cb9",
|
||||
"src/agentkit/server/frontend/src/views/SettingsView.vue": "a986f8bdb21cfe15",
|
||||
"src/agentkit/server/frontend/src/views/SkillsView.vue": "617903074c9d2941",
|
||||
"src/agentkit/server/frontend/src/views/TerminalView.vue": "63ac6fc0787aea9d",
|
||||
"src/agentkit/server/frontend/src/views/WorkflowView.vue": "3b5d6c6e1f585f69",
|
||||
"src/agentkit/server/frontend/tsconfig.json": "dd37dcb70fdb4d9a",
|
||||
"src/agentkit/server/frontend/tsconfig.node.json": "80d15af8bdd98d4b",
|
||||
"src/agentkit/server/frontend/vite.config.ts": "3680082296b63e43",
|
||||
"src/agentkit/server/middleware.py": "96b05f6f8063241d",
|
||||
"src/agentkit/server/routes/__init__.py": "1b79a3ccfc6a066b",
|
||||
"src/agentkit/server/routes/agents.py": "21f2555a100d026a",
|
||||
"src/agentkit/server/routes/chat.py": "104dbdffea2e444c",
|
||||
"src/agentkit/server/routes/evolution.py": "fce7e8d39d81d71b",
|
||||
"src/agentkit/server/routes/evolution_dashboard.py": "d8cdddb6f1f31a2f",
|
||||
"src/agentkit/server/routes/health.py": "6b80279471d80b96",
|
||||
"src/agentkit/server/routes/kb_management.py": "4e2805fe7e5cefcc",
|
||||
"src/agentkit/server/routes/llm.py": "41528730c7e8fc23",
|
||||
"src/agentkit/server/routes/memory.py": "1c80383f27ea9d06",
|
||||
"src/agentkit/server/routes/metrics.py": "00e89aa44374d486",
|
||||
"src/agentkit/server/routes/portal.py": "f84fd0fd3765a473",
|
||||
"src/agentkit/server/routes/settings.py": "d4e099566030cdd3",
|
||||
"src/agentkit/server/routes/skill_management.py": "18b59ecc2101a983",
|
||||
"src/agentkit/server/routes/skills.py": "d8af03b75ee51a7d",
|
||||
"src/agentkit/server/routes/tasks.py": "5da229c6eeee665d",
|
||||
"src/agentkit/server/routes/terminal.py": "54a7be8b03545753",
|
||||
"src/agentkit/server/routes/workflows.py": "36a26fff758c1d46",
|
||||
"src/agentkit/server/routes/ws.py": "610ea335d8dc8029",
|
||||
"src/agentkit/server/runner.py": "d9dcdc9dbcead3a2",
|
||||
"src/agentkit/server/static/assets/AgentLayout-DRofOCle.css": "624fe10c57377b9f",
|
||||
"src/agentkit/server/static/assets/AppLayout-D3vb9nEe.css": "da60a0d0bcd6f73b",
|
||||
"src/agentkit/server/static/assets/ChatView-pABfekuB.css": "4c85ceda245e4296",
|
||||
"src/agentkit/server/static/assets/ComputerUseView-DLnWxFj5.css": "634489f6de671317",
|
||||
"src/agentkit/server/static/assets/EvolutionView-CYpO52XJ.css": "1093af8009b6d07e",
|
||||
"src/agentkit/server/static/assets/KnowledgeBaseView-B7BP9eFg.css": "897bd5863b866a4b",
|
||||
"src/agentkit/server/static/assets/SettingsView-Cux44Hx9.css": "5d814d3e6e2aad80",
|
||||
"src/agentkit/server/static/assets/SkillsView-CD6l4lTk.css": "01d3f36c1c81d634",
|
||||
"src/agentkit/server/static/assets/TerminalView-Dg1PpXnU.css": "f700710a00b17f72",
|
||||
"src/agentkit/server/static/assets/WorkflowView-DRk6nEaR.css": "34e060faceedeaa8",
|
||||
"src/agentkit/server/static/assets/index-De1g9qb4.css": "440d66ee03cf0385",
|
||||
"src/agentkit/server/task_store.py": "48b0fa6b93eedd02",
|
||||
"src/agentkit/session/__init__.py": "4c9b3ddcd033cdfd",
|
||||
"src/agentkit/session/manager.py": "6970d7f8e84533c9",
|
||||
"src/agentkit/session/models.py": "73142e2bd83acf39",
|
||||
"src/agentkit/session/store.py": "afc3cb368d3eda2d",
|
||||
"src/agentkit/skills/__init__.py": "da932fee48b46389",
|
||||
"src/agentkit/skills/base.py": "21f6dd7a8ce8e743",
|
||||
"src/agentkit/skills/geo_pipeline.py": "3d24462bf74772d6",
|
||||
"src/agentkit/skills/loader.py": "894ad8633fbd6a71",
|
||||
"src/agentkit/skills/pipeline.py": "5337561cd632fa86",
|
||||
"src/agentkit/skills/registry.py": "8fb961ea4694a0c8",
|
||||
"src/agentkit/skills/schema.py": "37b34d43e7d4c872",
|
||||
"src/agentkit/skills/skill_md.py": "50115a8f8c7daf39",
|
||||
"src/agentkit/telemetry/__init__.py": "0196a5ba60a48373",
|
||||
"src/agentkit/telemetry/metrics.py": "2c137c5e2afc0219",
|
||||
"src/agentkit/telemetry/setup.py": "d80b0163b2b40e4b",
|
||||
"src/agentkit/telemetry/tracer.py": "2febd113b9aefaff",
|
||||
"src/agentkit/telemetry/tracing.py": "0de031c9690f1084",
|
||||
"src/agentkit/tools/__init__.py": "3f38137ac910b75e",
|
||||
"src/agentkit/tools/agent_tool.py": "40dd8fc67609c1c7",
|
||||
"src/agentkit/tools/ask_human.py": "0af1dce2de198057",
|
||||
"src/agentkit/tools/baidu_search.py": "f1fc70895adf4c86",
|
||||
"src/agentkit/tools/base.py": "f8051c91e7b2c870",
|
||||
"src/agentkit/tools/composition.py": "1399400373a7bae1",
|
||||
"src/agentkit/tools/computer_use.py": "07fd6142ba572caf",
|
||||
"src/agentkit/tools/computer_use_recorder.py": "b128cf30a8194210",
|
||||
"src/agentkit/tools/computer_use_session.py": "07e93efbf41e56bb",
|
||||
"src/agentkit/tools/function_tool.py": "d965b795e7aa971a",
|
||||
"src/agentkit/tools/headroom_retrieve.py": "78c9f452e2884b56",
|
||||
"src/agentkit/tools/mcp_tool.py": "fdf613db4e05386a",
|
||||
"src/agentkit/tools/memory_tool.py": "f0e43e260a066b41",
|
||||
"src/agentkit/tools/output_parser.py": "b5979893bc8751a0",
|
||||
"src/agentkit/tools/pty_session.py": "a59eb84476a1d233",
|
||||
"src/agentkit/tools/registry.py": "d9f431fde32e23da",
|
||||
"src/agentkit/tools/schema_tools.py": "d06b2ebb68137eae",
|
||||
"src/agentkit/tools/shell.py": "d979a37c206abb75",
|
||||
"src/agentkit/tools/skill_install.py": "7f6df2be83f0a974",
|
||||
"src/agentkit/tools/terminal_session.py": "09a52ee902faadf8",
|
||||
"src/agentkit/tools/web_crawl.py": "7d33adfa513583f9",
|
||||
"src/agentkit/tools/web_search.py": "32c04419179a503b",
|
||||
"src/agentkit/utils/__init__.py": "c4cde77152627568",
|
||||
"src/agentkit/utils/security.py": "64a377f0f9af299b",
|
||||
"src/agentkit/utils/vector_math.py": "95fbdc879f63c9f6",
|
||||
"tests/__init__.py": "d41d8cd98f00b204",
|
||||
"tests/conftest.py": "e25a6080360bb392",
|
||||
"tests/integration/__init__.py": "d41d8cd98f00b204",
|
||||
"tests/integration/conftest.py": "35362f98fbcf007d",
|
||||
"tests/integration/test_agent_lifecycle.py": "45ca2361ae89d2dc",
|
||||
"tests/integration/test_agent_v2_lifecycle.py": "813c0f11a440e191",
|
||||
"tests/integration/test_chat_adaptive_e2e.py": "3c29dadefff87012",
|
||||
"tests/integration/test_coding_harness_pipeline.py": "c07c587bdf135085",
|
||||
"tests/integration/test_evolution_loop.py": "af13640b3c042c8f",
|
||||
"tests/integration/test_gap_closure.py": "5445e0618f1570c7",
|
||||
"tests/integration/test_geo_compression.py": "e97a1e18fec7de33",
|
||||
"tests/integration/test_geo_e2e.py": "6ab31754e519e7e4",
|
||||
"tests/integration/test_goal_driven_scenario.py": "d4ff1c1d1095712c",
|
||||
"tests/integration/test_marketplace_e2e.py": "f9953b64d5fec881",
|
||||
"tests/integration/test_mcp_roundtrip.py": "d7b61599fa38f9fb",
|
||||
"tests/integration/test_merged_router.py": "4ebbb09628b4e5ec",
|
||||
"tests/integration/test_p0_hardening.py": "a64e9748554c3af5",
|
||||
"tests/integration/test_parallel_tools.py": "835e923a8ed8f0f6",
|
||||
"tests/integration/test_react_loop.py": "6600439989c27015",
|
||||
"tests/integration/test_reflexion_loop.py": "44cd7bd06b36311d",
|
||||
"tests/integration/test_rewoo_configurable_fallback.py": "22d188945fcfcace",
|
||||
"tests/integration/test_rewoo_fallback.py": "b0d90d02ad2e376a",
|
||||
"tests/integration/test_router_engine_chain.py": "298d027b3fb536e0",
|
||||
"tests/integration/test_server_e2e.py": "b18dd4834e6c9294",
|
||||
"tests/integration/test_soul_evolution_trigger.py": "395fa9a1c00ccf84",
|
||||
"tests/integration/test_tool_composition.py": "ca26b34ba7ac9fb4",
|
||||
"tests/test_routing_chain.py": "dc4bbb716852dc24",
|
||||
"tests/unit/__init__.py": "d41d8cd98f00b204",
|
||||
"tests/unit/conftest.py": "d957c6d325cecf73",
|
||||
"tests/unit/core/__init__.py": "d41d8cd98f00b204",
|
||||
"tests/unit/core/test_plan_checker.py": "a19366b76820465f",
|
||||
"tests/unit/core/test_plan_executor.py": "c91e6cf7aa90421d",
|
||||
"tests/unit/evolution/__init__.py": "d41d8cd98f00b204",
|
||||
"tests/unit/evolution/test_experience_store.py": "95ac34f0aa4eff18",
|
||||
"tests/unit/evolution/test_path_optimizer.py": "061f66cc4818ca25",
|
||||
"tests/unit/evolution/test_pitfall_detector.py": "456b21abec2630ed",
|
||||
"tests/unit/llm/test_usage_store.py": "d35fdbc8ad8c8807",
|
||||
"tests/unit/memory/__init__.py": "d41d8cd98f00b204",
|
||||
"tests/unit/memory/test_adapters.py": "bf54ecf5caaba82a",
|
||||
"tests/unit/memory/test_document_loader.py": "a85a73dacefded5b",
|
||||
"tests/unit/memory/test_local_rag.py": "d1bb69b64a704ac6",
|
||||
"tests/unit/memory/test_multi_source_rag.py": "2a90418f99d39a38",
|
||||
"tests/unit/quality/test_cascade_state_store.py": "373a5015bc25f010",
|
||||
"tests/unit/server/__init__.py": "d41d8cd98f00b204",
|
||||
"tests/unit/server/test_evolution_dashboard.py": "8b1882eb0e28c0fe",
|
||||
"tests/unit/server/test_kb_management.py": "ba66ac5d81a89675",
|
||||
"tests/unit/server/test_portal_routes.py": "5096d6fb11f916e4",
|
||||
"tests/unit/server/test_settings_routes.py": "c958e137d691bbdb",
|
||||
"tests/unit/server/test_skill_management.py": "7c2ab7ca496c2238",
|
||||
"tests/unit/server/test_terminal_routes.py": "473c2f0453f7bce5",
|
||||
"tests/unit/server/test_workflow_routes.py": "43f994ebbf8c14a1",
|
||||
"tests/unit/skills/__init__.py": "d41d8cd98f00b204",
|
||||
"tests/unit/skills/test_skill_registry_v2.py": "09566589524e0eb7",
|
||||
"tests/unit/test_ab_tester.py": "ddc218d7db08297a",
|
||||
"tests/unit/test_agent_bus.py": "7b4ec0c38781e682",
|
||||
"tests/unit/test_agent_pool.py": "6b0ed7625bfc0ca9",
|
||||
"tests/unit/test_agent_tool.py": "1d90fc78af703235",
|
||||
"tests/unit/test_alignment_guard.py": "e8dd184b11c82084",
|
||||
"tests/unit/test_anthropic_provider.py": "d15f53edc11cb259",
|
||||
"tests/unit/test_ask_human_tool.py": "a097f78a6d3161ea",
|
||||
"tests/unit/test_async_tasks.py": "f67efcc208f8ebae",
|
||||
"tests/unit/test_auction.py": "854f6a6ac5d810b6",
|
||||
"tests/unit/test_base_agent.py": "527b0607af6b3d0b",
|
||||
"tests/unit/test_base_agent_v2.py": "8b34903756625ed8",
|
||||
"tests/unit/test_bus_protocol.py": "cc278c537b9ab55a",
|
||||
"tests/unit/test_chat_memory_integration.py": "e04eedb7d6769bb9",
|
||||
"tests/unit/test_chat_routes.py": "ac9fd896485fad70",
|
||||
"tests/unit/test_chinese_providers.py": "8b7c6b1db3aed927",
|
||||
"tests/unit/test_cli.py": "391386a1f4b59780",
|
||||
"tests/unit/test_compression_config.py": "6e789f9e0b0e163d",
|
||||
"tests/unit/test_compression_strategy.py": "a354bb2c7caf96da",
|
||||
"tests/unit/test_config_driven.py": "c6110f498ad0685e",
|
||||
"tests/unit/test_context_compressor.py": "99d074bd33277d71",
|
||||
"tests/unit/test_contextual_retrieval.py": "c67f8f0c3cd088d1",
|
||||
"tests/unit/test_cost_aware_router.py": "c46fd3ad7d16d9b2",
|
||||
"tests/unit/test_dispatcher.py": "093c243e1fd88c53",
|
||||
"tests/unit/test_embedding_cache.py": "63f6898cd4c08cf8",
|
||||
"tests/unit/test_episodic_memory.py": "efd3fed7476b01fa",
|
||||
"tests/unit/test_episodic_vector_search.py": "d512a403d915d17c",
|
||||
"tests/unit/test_evolution.py": "267c0d997ac4b69b",
|
||||
"tests/unit/test_evolution_api.py": "c8bc3257089f2f28",
|
||||
"tests/unit/test_evolution_integration.py": "97dbf613d90cfade",
|
||||
"tests/unit/test_evolution_lifecycle.py": "c54cba680de3c6cf",
|
||||
"tests/unit/test_evolution_store.py": "aeede35adc6298bb",
|
||||
"tests/unit/test_evolution_store_persistent.py": "0a4f2e0b68352bf4",
|
||||
"tests/unit/test_execution_modes.py": "721e3dbd3bbe42b6",
|
||||
"tests/unit/test_fitness.py": "a8574c829dd1e6b8",
|
||||
"tests/unit/test_gateway_cache.py": "b90d19fe913acd6b",
|
||||
"tests/unit/test_gemini_provider.py": "a11569acda793b28",
|
||||
"tests/unit/test_genetic_evolution.py": "2298a952c78a1d57",
|
||||
"tests/unit/test_geo_pipeline.py": "0fbea766ae4127ff",
|
||||
"tests/unit/test_goal_planner.py": "41a75d8d954b9be2",
|
||||
"tests/unit/test_handoff.py": "b62bd295b820e7f0",
|
||||
"tests/unit/test_headroom_compressor.py": "690070348dedc54f",
|
||||
"tests/unit/test_headroom_retrieve_tool.py": "9cd59c7ef33abdf8",
|
||||
"tests/unit/test_http_rag_service.py": "c9553d7192028003",
|
||||
"tests/unit/test_intent_router.py": "d6d25ef448de860b",
|
||||
"tests/unit/test_llm_cache.py": "40d438e5249bffa3",
|
||||
"tests/unit/test_llm_gateway.py": "bc60cbfd1cab0078",
|
||||
"tests/unit/test_llm_protocol.py": "aa3e78b428e69995",
|
||||
"tests/unit/test_llm_provider.py": "596744095843d1ff",
|
||||
"tests/unit/test_llm_reflector.py": "703d9262357056ce",
|
||||
"tests/unit/test_llm_retry.py": "24b9eeb9b333a1e8",
|
||||
"tests/unit/test_mcp_client.py": "a41cb2b1f7a2ef9f",
|
||||
"tests/unit/test_mcp_config.py": "81b92487b8d81f0b",
|
||||
"tests/unit/test_mcp_manager.py": "8536847fdc4cd5ea",
|
||||
"tests/unit/test_mcp_server.py": "5a27613e90d76aae",
|
||||
"tests/unit/test_mcp_transport.py": "41d5b3080f524384",
|
||||
"tests/unit/test_memory_api.py": "bdbc0f4ee07e6f3f",
|
||||
"tests/unit/test_memory_integration.py": "c65d5c3312dfaf06",
|
||||
"tests/unit/test_memory_profile.py": "536e91f76c288475",
|
||||
"tests/unit/test_memory_retriever.py": "fe00ea44c651dd61",
|
||||
"tests/unit/test_memory_system.py": "cfb1ed2fcf7b3d2b",
|
||||
"tests/unit/test_memory_tool.py": "14b9fa7c03ca094d",
|
||||
"tests/unit/test_observability.py": "bc2708134d2e52ac",
|
||||
"tests/unit/test_onboarding.py": "b3fbdf5aa374ede9",
|
||||
"tests/unit/test_orchestrator.py": "93feb0c97e569e8c",
|
||||
"tests/unit/test_orchestrator_adaptive.py": "4b3204dd2e649de8",
|
||||
"tests/unit/test_orchestrator_bus.py": "20392261e563eedc",
|
||||
"tests/unit/test_orchestrator_integration.py": "5e5a16aac7a0f1d5",
|
||||
"tests/unit/test_org_context.py": "d1bad982d17ef585",
|
||||
"tests/unit/test_output_standardizer.py": "9305eddc864f0c3f",
|
||||
"tests/unit/test_pipeline.py": "dd3d8845733456f6",
|
||||
"tests/unit/test_pipeline_adversarial.py": "af99d36831acec26",
|
||||
"tests/unit/test_pipeline_compensation.py": "2acc0c4bd0126cc8",
|
||||
"tests/unit/test_pipeline_reflection.py": "d171b373e96620b4",
|
||||
"tests/unit/test_pipeline_retry.py": "b50bba77dc357937",
|
||||
"tests/unit/test_pipeline_state.py": "65df456e2d6054c5",
|
||||
"tests/unit/test_plan_exec_engine.py": "18c76d5d4f010b7d",
|
||||
"tests/unit/test_prompt_optimizer.py": "06325696628d2f69",
|
||||
"tests/unit/test_prompt_section.py": "e764eca7b44cc6c8",
|
||||
"tests/unit/test_prompt_template.py": "e57af046cdb3b4e4",
|
||||
"tests/unit/test_protocol.py": "da661f7a951b6576",
|
||||
"tests/unit/test_quality_gate.py": "a1b9a0ce009e3d8d",
|
||||
"tests/unit/test_query_transformer.py": "7fde4b222ae1f145",
|
||||
"tests/unit/test_rag_loop.py": "f1347eac9a5919a2",
|
||||
"tests/unit/test_ragas_evaluator.py": "521786f58e3fbb2f",
|
||||
"tests/unit/test_react_compression.py": "40a45ee367d84810",
|
||||
"tests/unit/test_react_engine.py": "62a5df9422ae21f4",
|
||||
"tests/unit/test_react_skill_mcp_integration.py": "6725ad4c70f8e4a9",
|
||||
"tests/unit/test_react_token_streaming.py": "14aa43e723cef9c6",
|
||||
"tests/unit/test_reflexion_engine.py": "689979f3c835aedb",
|
||||
"tests/unit/test_registry.py": "8d31cc0eee9cd89d",
|
||||
"tests/unit/test_retrieval_config.py": "a09fca0f1d8c44b2",
|
||||
"tests/unit/test_retrieve_knowledge_tool.py": "d12b7414e129f593",
|
||||
"tests/unit/test_rewoo_engine.py": "1fbd72b1923c3e24",
|
||||
"tests/unit/test_schema_tools.py": "a600b44b4c2ec894",
|
||||
"tests/unit/test_semantic_router.py": "a277ea3cb0bdd4d3",
|
||||
"tests/unit/test_server_config.py": "e86663f135a8396c",
|
||||
"tests/unit/test_server_middleware.py": "660766db5bdf4fb0",
|
||||
"tests/unit/test_server_routes.py": "3370e164dbac52ea",
|
||||
"tests/unit/test_session_manager.py": "90ef9929f4910c4b",
|
||||
"tests/unit/test_session_models.py": "2297ebde41ac1961",
|
||||
"tests/unit/test_session_store.py": "124898baeef2c549",
|
||||
"tests/unit/test_shell_tool.py": "fc1237230c684e25",
|
||||
"tests/unit/test_skill_config.py": "f3aef1188e101bac",
|
||||
"tests/unit/test_skill_loader.py": "21b83961057e4fcb",
|
||||
"tests/unit/test_skill_md.py": "a78f997dbe75695b",
|
||||
"tests/unit/test_skill_pipeline.py": "b51de8bf81f193d4",
|
||||
"tests/unit/test_skill_registry.py": "30679e6242902a3e",
|
||||
"tests/unit/test_soul_evolution.py": "07d8b0b4550142a6",
|
||||
"tests/unit/test_stdio_transport.py": "ad7d51c748b3580f",
|
||||
"tests/unit/test_streaming.py": "9430a86a4cae4435",
|
||||
"tests/unit/test_task_store_redis.py": "31e1ed3cb10dab5b",
|
||||
"tests/unit/test_telemetry.py": "2f957631f9d87522",
|
||||
"tests/unit/test_tool_composition.py": "88c496199e43eab1",
|
||||
"tests/unit/test_tool_registry.py": "cb930ed167fe2b23",
|
||||
"tests/unit/test_trace_recorder.py": "0f7809fe85094c08",
|
||||
"tests/unit/test_u8_geo_integration.py": "c0eb3468de53fe1e",
|
||||
"tests/unit/test_unified_evolution_store.py": "8419ece33016b902",
|
||||
"tests/unit/test_usage_tracker.py": "eed60bc0fcf6a4a3",
|
||||
"tests/unit/test_web_crawl_tool.py": "13d5bb6f6b098410",
|
||||
"tests/unit/test_web_search_tool.py": "7ee8a8b20b793e03",
|
||||
"tests/unit/test_websocket.py": "53e9ecd8c70f5bfe",
|
||||
"tests/unit/test_working_memory.py": "49016e18a7998c4a",
|
||||
"tests/unit/tools/__init__.py": "d41d8cd98f00b204",
|
||||
"tests/unit/tools/test_computer_use.py": "1f851f50f5eb5e44",
|
||||
"tests/unit/tools/test_pty_session.py": "8a512aabb314036e",
|
||||
"tests/unit/tools/test_terminal_session.py": "1e979327e0d14753"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"lastAnalyzedAt": "2026-06-17T05:30:00.000000+00:00",
|
||||
"gitCommitHash": "840d1af4f7a3c1b5e8d2c6a9f0e3b7d5h6i8j0k2",
|
||||
"version": "1.0.0",
|
||||
"analyzedFiles": 2418,
|
||||
"lastUpdateSummary": "fix: resolve benchmark failures from root cause (LLM timeout, WebSocket, latency stats)"
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ config:
|
|||
collaboration_strategy: "cooperative"
|
||||
bound_skills: []
|
||||
avatar: "A"
|
||||
color: "#07C160"
|
||||
color: "#1e40af"
|
||||
is_lead: false
|
||||
task_mode: llm_generate
|
||||
prompt:
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ config:
|
|||
collaboration_strategy: "cooperative"
|
||||
bound_skills: []
|
||||
avatar: "B"
|
||||
color: "#fa8c16"
|
||||
color: "#155e75"
|
||||
is_lead: false
|
||||
task_mode: llm_generate
|
||||
prompt:
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ config:
|
|||
collaboration_strategy: "cooperative"
|
||||
bound_skills: []
|
||||
avatar: "C"
|
||||
color: "#2C3E50"
|
||||
color: "#166534"
|
||||
is_lead: false
|
||||
task_mode: llm_generate
|
||||
prompt:
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ config:
|
|||
collaboration_strategy: "cooperative"
|
||||
bound_skills: []
|
||||
avatar: "C"
|
||||
color: "#722ed1"
|
||||
color: "#1f2937"
|
||||
is_lead: false
|
||||
task_mode: llm_generate
|
||||
prompt:
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ config:
|
|||
- qa_engineer
|
||||
- code_reviewer
|
||||
avatar: "D"
|
||||
color: "#1890ff"
|
||||
color: "#6b7280"
|
||||
is_lead: false
|
||||
task_mode: llm_generate
|
||||
prompt:
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ config:
|
|||
collaboration_strategy: "cooperative"
|
||||
bound_skills: []
|
||||
avatar: "E"
|
||||
color: "#E31937"
|
||||
color: "#9ca3af"
|
||||
is_lead: false
|
||||
task_mode: llm_generate
|
||||
prompt:
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ config:
|
|||
collaboration_strategy: "cooperative"
|
||||
bound_skills: []
|
||||
avatar: "F"
|
||||
color: "#52c41a"
|
||||
color: "#1e40af"
|
||||
is_lead: false
|
||||
task_mode: llm_generate
|
||||
prompt:
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ config:
|
|||
collaboration_strategy: "cooperative"
|
||||
bound_skills: []
|
||||
avatar: "J"
|
||||
color: "#FF9900"
|
||||
color: "#92400e"
|
||||
is_lead: false
|
||||
task_mode: llm_generate
|
||||
prompt:
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ config:
|
|||
collaboration_strategy: "cooperative"
|
||||
bound_skills: []
|
||||
avatar: "P"
|
||||
color: "#FF6600"
|
||||
color: "#92400e"
|
||||
is_lead: false
|
||||
task_mode: llm_generate
|
||||
prompt:
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ config:
|
|||
- charlie_munger
|
||||
- paul_graham
|
||||
avatar: "P"
|
||||
color: "#8E44AD"
|
||||
color: "#7c2d12"
|
||||
is_lead: false
|
||||
task_mode: llm_generate
|
||||
prompt:
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ config:
|
|||
collaboration_strategy: "cooperative"
|
||||
bound_skills: []
|
||||
avatar: "Q"
|
||||
color: "#eb2f96"
|
||||
color: "#374151"
|
||||
is_lead: false
|
||||
task_mode: llm_generate
|
||||
prompt:
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ config:
|
|||
collaboration_strategy: "cooperative"
|
||||
bound_skills: []
|
||||
avatar: "R"
|
||||
color: "#1A5276"
|
||||
color: "#6b7280"
|
||||
is_lead: false
|
||||
task_mode: llm_generate
|
||||
prompt:
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ config:
|
|||
collaboration_strategy: "cooperative"
|
||||
bound_skills: []
|
||||
avatar: "S"
|
||||
color: "#555555"
|
||||
color: "#1e40af"
|
||||
is_lead: false
|
||||
task_mode: llm_generate
|
||||
prompt:
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ config:
|
|||
collaboration_strategy: "cooperative"
|
||||
bound_skills: []
|
||||
avatar: "T"
|
||||
color: "#1890ff"
|
||||
color: "#92400e"
|
||||
is_lead: true
|
||||
task_mode: llm_generate
|
||||
prompt:
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ config:
|
|||
collaboration_strategy: "cooperative"
|
||||
bound_skills: []
|
||||
avatar: "W"
|
||||
color: "#1E8449"
|
||||
color: "#78716c"
|
||||
is_lead: false
|
||||
task_mode: llm_generate
|
||||
prompt:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
version: "3.8"
|
||||
|
||||
# =============================================================================
|
||||
# 开发环境 override
|
||||
# =============================================================================
|
||||
# 用法:docker compose -f docker-compose.yaml -f docker-compose.dev.yml up -d redis postgres
|
||||
#
|
||||
# 仅启动 redis + postgres(agentkit 在宿主机运行),映射端口到 6381/5435
|
||||
# 避免与 pms-redis(6379) / geo_redis(6380) / geo_db(5433) 端口冲突
|
||||
#
|
||||
# .env.dev 应包含:
|
||||
# REDIS_URL=redis://127.0.0.1:6381/0
|
||||
# DATABASE_URL=postgresql+asyncpg://agentkit:agentkit@127.0.0.1:5435/agentkit
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
# 开发模式不启动 agentkit 容器(在宿主机运行)
|
||||
agentkit:
|
||||
profiles: ["never"]
|
||||
|
||||
redis:
|
||||
ports:
|
||||
- "6381:6379"
|
||||
|
||||
postgres:
|
||||
ports:
|
||||
- "5435:5432"
|
||||
|
|
@ -1,5 +1,12 @@
|
|||
version: "3.8"
|
||||
|
||||
# =============================================================================
|
||||
# 生产部署配置
|
||||
# =============================================================================
|
||||
# 启动:docker compose up -d
|
||||
# agentkit 容器内通过 service name (redis/postgres) 连接,不暴露中间件端口
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
agentkit:
|
||||
build: .
|
||||
|
|
@ -8,8 +15,12 @@ services:
|
|||
- "8001:8001"
|
||||
env_file: .env
|
||||
environment:
|
||||
# 容器间通信:使用 service name,不依赖宿主机端口
|
||||
- REDIS_URL=redis://redis:6379/0
|
||||
- DATABASE_URL=postgresql+asyncpg://agentkit:agentkit@postgres:5432/agentkit
|
||||
- AGENTKIT_BUS_BACKEND=redis
|
||||
- AGENTKIT_SESSION_BACKEND=redis
|
||||
- AGENTKIT_TASK_STORE_BACKEND=redis
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
|
@ -25,8 +36,9 @@ services:
|
|||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
# 生产模式不暴露端口到宿主机,仅容器间可达
|
||||
expose:
|
||||
- "6379"
|
||||
volumes:
|
||||
- redisdata:/data
|
||||
healthcheck:
|
||||
|
|
@ -38,8 +50,9 @@ services:
|
|||
|
||||
postgres:
|
||||
image: pgvector/pgvector:pg15
|
||||
ports:
|
||||
- "5432:5432"
|
||||
# 生产模式不暴露端口到宿主机,仅容器间可达
|
||||
expose:
|
||||
- "5432"
|
||||
environment:
|
||||
POSTGRES_USER: agentkit
|
||||
POSTGRES_PASSWORD: agentkit
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
---
|
||||
date: 2026-07-02
|
||||
topic: private-board-restrictions-and-scheme-b-bubbles
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
收尾私董会(@board)模块的 UI 细节与单会话状态约束。三处改动:(1) 限制一个对话只能存在一个私董会,新建私董会必须从新会话发起;(2) 将 `BoardBannerCard`(私董会开始卡片)从带边框 / 紫条 / 进度条 / 专家 chip 的重样式简化为单行标题 + 副标题;(3) 给所有 `AssistantText` 渲染添加方案 B 风格的浅灰圆角矩形气泡,与方案 B 截图保持一致。
|
||||
|
||||
## Problem Frame
|
||||
|
||||
私董会功能自 2026-06 上线以来已能正确触发多轮讨论,但 UI 上仍残留三处粗糙点:
|
||||
|
||||
1. **单会话多私董会无约束**。`ChatInput.vue:75` 的"私董会"按钮 `@click` 直接 `showBoardModal = true`,对当前会话是否已经存在私董会无任何判断。后端在 `board_started` 事件中没有按会话做去重,连续两次 `SendMessage("@board:...")` 会在同一会话里创建第二个私董会,叠加在第一个未结束的私董会之上,`boardState.experts` 被覆盖、轮次错乱、`StickyModeHeader` 头像数与实际不符。
|
||||
2. **`BoardBannerCard` 样式过重**。`BoardBannerCard.vue:55-137` 使用了 `background / border / border-radius / box-shadow` 四件套 + 4px 紫条 + 进度条 + 专家 chip pill,与方案 B 整体"克制、不重样式"的取向冲突。方案 B 截图(参考 `docs/.../2026-06-18-chat-area-vi-redesign-requirements.md` 中的方案 B 示意)的"开始"标题区域是单行文本,不带装饰。
|
||||
3. **方案 B 气泡未落地**。方案 B 截图中的"专家发言"区域是**有**浅灰圆角矩形气泡包裹内容(不是无气泡),与 ChatGPT / Notion AI 风格一致。当前 `AssistantText.vue:1-30` 的内容区 `.assistant-text` 没有背景 / 边框 / 圆角,气泡效果完全缺失。
|
||||
|
||||
## Key Decisions
|
||||
|
||||
- **私董会限制的"已存在"判断以 `boardState.status` 为准**:`status === 'discussing' | 'concluding'` 时禁止在当前会话再次发起;`status === 'completed' | 'dissolved' | null` 时允许(已完成 / 已解散的旧私董会不阻塞新私董会)。判断点放在 `ChatInput.vue` "私董会"按钮 `@click` 处(最自然的 UX 拦截点),不放在 `BoardMeetingModal` 内部(避免用户填表后才发现不能发起)。
|
||||
- **私董会限制的反馈方式是 a-modal 弹窗 + 快捷新建按钮**。点击"私董会"按钮时若检测到已有私董会,弹出 `a-modal`,标题"当前会话已存在私董会",副文"请新建会话来创建新的私董会",按钮"我知道了" + "新建会话"。"新建会话"按钮直接调用 `chatStore.createConversation()` 并 `chatStore.selectConversation(newId)`,让用户立即在新会话里继续操作。
|
||||
- **BoardBannerCard 简化为单行标题 + 副标题**。完全去掉 `BankOutlined` 图标、专家 chip 列表、4px 紫条、进度条、卡片背景 / 边框 / 圆角 / 阴影。最终输出形如:
|
||||
|
||||
```
|
||||
私董会 — 利用 agent 实现私董会的功能,应该用什么功能来打动客户
|
||||
轮次:第 1 / 5 轮
|
||||
```
|
||||
|
||||
标题字号 = `var(--font-base)` 加粗 + 主题文本;副标题字号 = `var(--font-xs)` + `var(--text-tertiary)`。`StickyModeHeader` 顶部的紫色"私董会"徽章 + 主题 + 专家头像组保持不变,承担需要"重样式"的展示职责。
|
||||
- **方案 B 浅灰气泡应用到所有 assistant 消息**。具体范围:`role === 'assistant'` 的所有 `MessageShell` 内的内容(普通 chat、@team 阶段、@board 发言、Debate、Plan exec、Tool result 等)都加同款浅灰圆角矩形气泡;`role === 'user'` 的用户消息气泡保留现有 `UserBubble.vue` 的右对齐独立样式,不加 AssistantText 风格的浅灰块。气泡使用 token 颜色(`var(--bg-secondary)` 或 `var(--bg-elevated)` 系)+ 圆角 `var(--radius-md)` + 内边距 `var(--space-3) var(--space-4)`,确保与方案 B 截图视觉一致;颜色与边框用 CSS 变量绑定,**禁止硬编码** `#f3f4f6` / `#fbfbfa` / `#ededec` 等值。
|
||||
- **气泡内的代码块、表格、行内代码样式保持不变**。`AssistantText.vue:257-401` 的 `pre / hljs / code / table` 样式已经在 dark-on-light 配色上做了适配(`--code-bg` / `--code-fg` / `--code-keyword` 等 token),气泡背景换浅灰后这些 token 自动适配,不需要单独再改。
|
||||
- **不触碰 StickyModeHeader 顶部条**。顶部条的紫色边框、徽章、4 个专家头像等不属于本次改动范围。
|
||||
|
||||
## Requirements
|
||||
|
||||
### 单会话私董会限制
|
||||
|
||||
- R1. `ChatInput.vue` 的"私董会"按钮 `@click` 处理函数在打开 `BoardMeetingModal` 前,先检查 `chatStore.boardState.value`:若非 null 且 `status === 'discussing' | 'concluding'`,触发 a-modal 弹窗,**不**打开 `BoardMeetingModal`。
|
||||
- R2. a-modal 弹窗内容:标题"当前会话已存在私董会",副文案"请新建会话来创建新的私董会",按钮"我知道了"(关闭弹窗)+ "新建会话"(主操作)。"新建会话"按钮点击后:(a) 关闭弹窗;(b) 调用 `chatStore.createConversation()`;(c) `await chatStore.selectConversation(newId)`;(d) 不自动打开 `BoardMeetingModal`,由用户在新会话中再次点击"私董会"按钮继续。
|
||||
- R3. 若 `boardState.value === null` 或 `status === 'completed' | 'dissolved'`,保持当前行为:`showBoardModal = true` 直接打开 `BoardMeetingModal`,不弹提示。
|
||||
- R4. 判定不依赖后端 / `is_board` 标记 / `conv.is_board`——以前端 `chatStore.boardState.value` 实时状态为权威源,避免 reload 后误判。
|
||||
|
||||
### 私董会开始卡片简化
|
||||
|
||||
- R5. `BoardBannerCard.vue` 重构为单行标题 + 副标题:保留 `topic / maxRounds / currentRound` props(向后兼容调用方),不再使用 `experts` props(删除 prop)。
|
||||
- R6. 模板输出仅两行:第一行 `私董会 — {topic}`(无 BankOutlined、无边框、无背景、无圆角、无 4px 紫条);第二行 `轮次:第 {currentRound} / {maxRounds} 轮`(小灰字)。
|
||||
- R7. `<style scoped>` 中删除 `.board-banner-card` / `.board-banner-card__bar` / `.board-banner-card__chip` 等所有重样式相关类;保留 `.board-banner-card__title` / `.board-banner-card__meta` 两条最小样式。
|
||||
- R8. `useMessageRenderer.ts` 中 `board_started` 的渲染路径不变(仍然渲染 `BoardBannerCard` 组件),确保 streaming 期间与 reload 后的视觉一致。
|
||||
|
||||
### AssistantText 浅灰气泡
|
||||
|
||||
- R9. `MessageShell.vue` 的 `.message-shell__content` 增加浅灰气泡样式:仅当 `role === 'assistant'` 时生效(`user` 角色不触发);`background: var(--bg-secondary)` + `border-radius: var(--radius-md)` + `padding: var(--space-3) var(--space-4)` + `border: 1px solid var(--border-color)` + `color: var(--text-primary)`。
|
||||
- R10. 气泡样式**不**使用 `!important`,不覆盖组件内已有的代码块 / 表格 / 路由 tag 样式(这些样式已经在 R-9 之外独立维护)。
|
||||
- R11. 浅灰气泡不影响 `BoardRoundCard` 内 `AssistantText` 的渲染(已统一走 `MessageShell` 槽位)。
|
||||
- R12. 私董会专家发言(`board_speech` / `board_summary`)、普通 chat assistant 消息、@team 阶段消息、Debate 消息都通过 `MessageShell` + `AssistantText` 渲染,因此统一获得 R-9 浅灰气泡,无需逐个组件加样式。
|
||||
- R13. 气泡宽度继承 `.message-shell__content` 的现有 `width: 100%; max-width: 100%`——气泡跟随消息列宽自适应,不强制固定最大宽度。
|
||||
|
||||
## Scope Boundaries
|
||||
|
||||
### Deferred for later
|
||||
|
||||
- **后端按会话去重 `board_started`**:本次只做前端拦截,**不**改后端逻辑。后端允许同一会话收到多次 `board_started` 是当前事实,本次不做接口变更;前端的"未在新会话发起"拦截在功能上等价于"阻止重复发起"。如果未来需要在多端 / 多浏览器同步场景下做强一致,再考虑后端校验。
|
||||
- **StickyModeHeader 顶部条的视觉细化**(徽章大小、专家头像间距、紫色边框粗细)不在本次范围。
|
||||
- **BoardConclusionCard / DebateBannerCard / TeamPlanCard 等其他 board 模式组件的样式统一**:本次只改 `BoardBannerCard`,其他卡片样式留待后续迭代。
|
||||
|
||||
### Outside this product's identity
|
||||
|
||||
- 不调整方案 B 调色板(`expertIdentity.ts` 的 12 色 PALETTE)和专家头像首字符规则。
|
||||
- 不调整私董会后端流程(`BoardOrchestrator` / `chatStream` 事件顺序)。
|
||||
- 不调整 AssistantText 的 markdown 渲染逻辑、代码高亮、表格样式。
|
||||
|
||||
## Dependencies / Assumptions
|
||||
|
||||
- 假设:`chatStore.createConversation()` 存在并返回新会话 id(`chatStore.ts:310` 已确认)。
|
||||
- 假设:`chatStore.selectConversation(id)` 可异步调用(`chatStore.ts:218` 已确认)。
|
||||
- 假设:`BoardState.status` 类型为 `"discussing" | "concluding" | "completed" | "dissolved"`(`chatStream.ts:65` 已确认),"已存在私董会"判断取 `discussing | concluding`。
|
||||
- 假设:`--bg-secondary` / `--radius-md` / `--space-3` / `--space-4` / `--border-color` / `--text-primary` 已在 `tokens.css` 中定义并被前端使用(`styles/tokens.css` 已确认存在)。
|
||||
- 不确定性:私董会可能存在的中间状态(`forming` / `executing` / `synthesizing` 等)目前不在 `BoardState.status` 联合中;如果未来增加新 status,本次"已存在"判断需扩展。
|
||||
|
|
@ -0,0 +1,393 @@
|
|||
---
|
||||
date: 2026-07-02
|
||||
type: feat
|
||||
title: 私董会单会话限制 + 方案B 气泡 + 简化开始卡片
|
||||
origin: docs/brainstorms/2026-07-02-private-board-restrictions-and-scheme-b-bubbles-requirements.md
|
||||
status: ready
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
收尾私董会(@board)模块的 UI 细节与单会话状态约束,四处协同改动:(1) 在 `ChatInput.vue` 的"私董会"按钮 click 处拦截"当前会话已存在进行中的私董会"场景,弹 a-modal 提示并提供快捷新建会话按钮;(2) 将 `BoardBannerCard.vue` 从带边框/紫条/进度条/专家 chip 的重样式简化为单行标题+副标题;(3) 给 `MessageShell.vue` 中所有 `role === 'assistant'` 的消息内容添加方案 B 风格的浅灰圆角矩形气泡(F1-A 独立 token / F4-A 排除 conclusion / D4-方案1 `:empty` 隐藏);(4) 将 `UserBubble.vue` 普通文本消息改为 demo 中的深色右对齐气泡(`--color-primary` + `--text-inverse`),@board/@team 命令卡片保持现有浅色背景。改动范围仅限前端 Vue 组件与少量 store/type/token 文件,不动后端、不动方案 B 调色板、不动 StickyModeHeader 顶部条。
|
||||
|
||||
## Problem Frame
|
||||
|
||||
私董会功能上线后存在三处 UI 粗糙点(详见 origin: docs/brainstorms/2026-07-02-private-board-restrictions-and-scheme-b-bubbles-requirements.md):
|
||||
|
||||
1. **单会话多私董会无约束**:`ChatInput.vue:75` 的"私董会"按钮 `@click` 直接 `showBoardModal = true`,对当前会话是否已存在私董会无任何判断。连续两次 `SendMessage("@board:...")` 会创建第二个私董会,叠加在第一个未结束的私董会之上,`boardState.experts` 被覆盖、轮次错乱、`StickyModeHeader` 头像数与实际不符。
|
||||
2. **BoardBannerCard 样式过重**:`BoardBannerCard.vue` 使用了 card+border+shadow+4px 紫条+进度条+专家 chip pill 等装饰,与方案 B 整体"克制、不重样式"取向冲突。方案 B 截图中的"开始"区域是单行文本,不带装饰。
|
||||
3. **方案 B 气泡未落地**:方案 B 截图中专家发言区域**有**浅灰圆角矩形气泡包裹内容,与 ChatGPT / Notion AI 风格一致。当前 `MessageShell.vue:178-184` 的 `.message-shell__content` 没有背景/边框/圆角,气泡效果完全缺失。
|
||||
|
||||
## Requirements
|
||||
|
||||
完整继承 origin 文档的 13 条 R-IDs(R1-R13)并新增 R14-R19(F4-A / D4-方案1 / U4 决策固化),共 19 条,分组如下:
|
||||
|
||||
**单会话私董会限制(R1-R4)**:
|
||||
- R1:`ChatInput.vue` "私董会"按钮 `@click` 在打开 `BoardMeetingModal` 前检查 `chatStore.boardState`,`status === 'discussing' | 'concluding'` 时弹 a-modal,**不**打开 modal
|
||||
- R2:a-modal 标题"当前会话已存在私董会",副文"请新建会话来创建新的私董会",按钮"我知道了"+ "新建会话"(主操作);"新建会话"流程:关 modal → `chatStore.createConversation()` → `chatStore.selectConversation(newId, true)` → 不自动打开 modal
|
||||
- R3:`boardState === null` 或 `status === 'completed' | 'dissolved'` 时保持当前行为
|
||||
- R4:以前端 `chatStore.boardState` 为权威源,不依赖后端 / `is_board` 标记
|
||||
|
||||
**BoardBannerCard 简化(R5-R8)**:
|
||||
- R5:重构为单行标题+副标题,保留 `topic / maxRounds / currentRound` props 向后兼容,删除 `experts` prop
|
||||
- R6:模板输出两行:`私董会 — {topic}` + `轮次:第 {currentRound} / {maxRounds} 轮`
|
||||
- R7:删除 `.board-banner-card` 的重样式(background/border/border-radius/box-shadow)及 `__bar / __chip` 等重样式类,保留 `.board-banner-card` 容器(仅 margin/padding)+ `__title / __meta` 最小样式
|
||||
- R8:`useMessageRenderer.ts` 中 `board_started` 渲染路径不变
|
||||
|
||||
**AssistantText 浅灰气泡(R9-R15)**:
|
||||
- R9:`MessageShell.vue` 的 `.message-shell__content` 加 `background: var(--bg-message-bubble)` + `border-radius: var(--radius-md)` + `padding: var(--space-3) var(--space-4)` + `border: 1px solid var(--border-color)` + `color: var(--text-primary)`,仅 `role === 'assistant'` 时生效。**F1-A 决策**:引入独立 token `--bg-message-bubble`(light `#ffffff` / dark `#1f1f1f`),与 `--bg-secondary`(inline code/table 背景)解耦,避免气泡背景与代码/表格背景视觉冲突
|
||||
- R10:不使用 `!important`,不覆盖代码块/表格/路由 tag 样式
|
||||
- R11:不影响 `BoardRoundCard` 内 `AssistantText` 渲染
|
||||
- R12:私董会专家发言、普通 chat、@team 阶段、Debate 等都通过 `MessageShell + AssistantText` 统一获得气泡
|
||||
- R13:气泡宽度继承现有 `width: 100%; max-width: 100%`,不强制固定最大宽度
|
||||
- R14:**F4-A 决策**:气泡选择器排除 `board_conclusion` 类型——`BoardConclusionCard` 自带 card chrome(background/border/border-radius/shadow),保留其自身样式,不再被气泡包裹。实现方式:在 `MessageShell.vue` 通过 `messageType` prop 或 `:not(:has(.board-conclusion-card))` 选择器排除
|
||||
- R15:**D4-方案1 决策**:空 slot 内容(pre-stream thinking / tool-call-only)时气泡不渲染背景——通过 `:empty` 选择器隐藏 `background / border / padding`,仅显示 thinking dots。有内容流入后自动恢复气泡样式
|
||||
|
||||
**UserBubble 普通文本深色气泡(R16-R19)**:
|
||||
- R16:`UserBubble.vue` 的普通文本消息(`<span class="user-bubble__text">`)样式改为 demo 中的深色右对齐气泡:`background: var(--color-primary)` + `color: var(--text-inverse)` + `padding: var(--space-3) var(--space-4)` + `max-width: 70%`
|
||||
- R17:@board/@team 命令卡片(`.user-bubble__command`)和文件附件保持现有 `--bg-tertiary` 浅色背景,不应用深色气泡样式
|
||||
- R18:dark mode 自动反转——`--color-primary` 在 dark mode 下为 `#fbfbfa`(浅),`--text-inverse` 为 `#1a1a1a`(深),user 气泡自动变为浅色背景 + 深色文字
|
||||
- R19:通过 `isPlainText` computed(`!fileAttachment && !commandBubble`)+ `.user-bubble--text` modifier class 区分普通文本与命令卡片,不修改 `.user-bubble` 默认样式
|
||||
|
||||
## Key Technical Decisions
|
||||
|
||||
- **拦截点选择 ChatInput 按钮 click,不放在 BoardMeetingModal 内部** — 最自然的 UX 拦截点,避免用户填表后才发现不能发起。判断依据 `chatStore.boardState` 实时状态,不依赖后端 / `is_board` 标记,避免 reload 后误判(见 origin R4)。
|
||||
- **私董会状态判定取 `discussing | concluding`** — `BoardState.status` 类型为 `"discussing" | "concluding" | "completed" | "dissolved"`(chatStream.ts:65 确认)。`completed | dissolved` 的旧私董会不阻塞新私董会发起,符合 origin R3 要求。
|
||||
- **"新建会话"按钮流程:`createConversation()` → `selectConversation(newId, true)`** — `createConversation()` 是同步函数(chatStore.ts:333 确认),自动设置 `currentConversationId` 但不加载服务端会话;后续 `selectConversation(newId, true)` 强制 reload 确保状态干净。不自动打开 `BoardMeetingModal`,由用户在新会话中再次点击"私董会"按钮继续(见 origin R2)。
|
||||
- **BoardBannerCard 简化为单行标题+副标题,不删除组件本身** — 保留 `BoardBannerCard.vue` 文件,仅重构 template + style。`useMessageRenderer.ts` 的 `board_started` 渲染路径不变(origin R8),保持 streaming 期间与 reload 后视觉一致。
|
||||
- **气泡样式挂在 `MessageShell.vue` 的 `.message-shell__content` 上,不挂在 `AssistantText.vue`** — `MessageShell` 是所有 assistant 消息的统一外壳,挂在这里可一次性覆盖 board_speech / board_summary / 普通聊天 / @team / Debate 等场景(origin R12),且不影响 `UserBubble.vue` 的 user 消息样式。**board_conclusion 例外**(F4-A):`BoardConclusionCard` 自带 card chrome,不被气泡包裹。
|
||||
- **气泡使用 CSS 变量,禁止硬编码** — 新增 `--bg-message-bubble: #ffffff` (light) / `#1f1f1f` (dark) 到 `tokens.css`,与 `--bg-secondary`(inline code/table 背景 `#fbfbfa`)解耦(F1-A 决策)。dark mode 自动切换。禁止硬编码 `#f3f4f6` / `#fbfbfa` / `#ffffff` 等值(project rules 硬约束)。
|
||||
- **`role === 'assistant'` 时启用气泡,`role === 'user'` 不启用** — 通过 `.message-shell--assistant .message-shell__content` 选择器限定,避免污染 user 消息的 `UserBubble` 样式。
|
||||
- **F4-A 排除 conclusion 类型** — `board_conclusion` 消息渲染的 `BoardConclusionCard` 自带 `background: var(--bg-primary)` + `border` + `border-radius: var(--radius-card)` + `box-shadow`,气泡选择器需排除该类型,避免"白卡片嵌套在白气泡里"的视觉冲突。实现方式:在 `MessageShell.vue` 增加 `messageType` prop(或 `:not(:has(.board-conclusion-card))` 选择器),conclusion 类型不加气泡样式。
|
||||
- **D4-方案1 `:empty` 隐藏空气泡** — 空 slot 内容(pre-stream thinking / tool-call-only)时通过 `:empty` 选择器隐藏 `background / border / padding`,仅显示 thinking dots 动画。有内容流入后自动恢复气泡样式,无需 JS 判断。
|
||||
- **U4 仅普通文本消息深色气泡,命令卡片保持浅色** — `UserBubble.vue` 同时渲染三种内容(普通文本 / @board|@team 命令卡片 / 文件附件),命令卡片含结构化信息(header + experts list),深色背景会降低可读性。通过 `isPlainText` computed + `.user-bubble--text` modifier 精确限定深色样式仅作用于普通文本,命令卡片和文件附件继承 `.user-bubble` 默认 `--bg-tertiary` 浅色背景。
|
||||
- **U4 用 `--color-primary` + `--text-inverse` 自动适配 dark mode** — `--color-primary` 在 light mode 为 `#1a1a1a`(深),dark mode 为 `#fbfbfa`(浅);`--text-inverse` 反之。user 气泡在 dark mode 下自动反转为浅色背景 + 深色文字,无需额外 dark mode 覆盖。
|
||||
|
||||
## Implementation Units
|
||||
|
||||
### U1. ChatInput 拦截 + a-modal 弹窗 + 快捷新建会话
|
||||
|
||||
**Goal:** 在 `ChatInput.vue` 的"私董会"按钮 click 处增加拦截逻辑:当前会话已存在进行中的私董会时弹 a-modal 提示,提供"我知道了"和"新建会话"两个按钮;"新建会话"按钮调用 `chatStore.createConversation()` + `selectConversation(newId, true)` 跳转到新空会话。
|
||||
|
||||
**Requirements:** R1, R2, R3, R4
|
||||
|
||||
**Dependencies:** 无
|
||||
|
||||
**Files:**
|
||||
- src/agentkit/server/frontend/src/components/chat/ChatInput.vue(修改)
|
||||
- src/agentkit/server/frontend/src/components/chat/\_\_tests\_\_/ChatInput.test.ts(新建)
|
||||
|
||||
**Approach:**
|
||||
- 在 `<template>` 中新增一个 `<a-modal v-model:open="showBoardBlockModal">`,标题"当前会话已存在私董会",内容"请新建会话来创建新的私董会",footer 两个按钮:`<a-button @click="showBoardBlockModal = false">我知道了</a-button>` + `<a-button type="primary" @click="handleCreateNewConversationForBoard">新建会话</a-button>`
|
||||
- 新增 `showBoardBlockModal = ref(false)` state
|
||||
- 新增 `handleBoardClick()` 函数替代 inline `@click="showBoardModal = true"`:
|
||||
- 检查 `chatStore.boardState?.status`:若为 `'discussing' | 'concluding'` → `showBoardBlockModal.value = true`,return
|
||||
- 否则 `showBoardModal.value = true`
|
||||
- 新增 `handleCreateNewConversationForBoard()` 函数:
|
||||
- `showBoardBlockModal.value = false`
|
||||
- `chatStore.createConversation()`
|
||||
- `await chatStore.selectConversation(chatStore.currentConversationId!, true)`
|
||||
- 不设置 `showBoardModal.value = true`(让用户在新会话中再次点击)
|
||||
- "私董会"按钮 `@click` 改为 `handleBoardClick`
|
||||
|
||||
**Patterns to follow:**
|
||||
- `ChatInput.vue:63-71` 的 `showTeamModal` 模式(v-model:open + ref + handleXxxSubmit)
|
||||
- `chatStore.ts:333-345` 的 `createConversation` 同步签名
|
||||
- `chatStore.ts:219` 的 `selectConversation(id, force = false)` 异步签名
|
||||
|
||||
**Test scenarios:**
|
||||
- Happy path:`chatStore.boardState === null` → 点击"私董会"按钮 → `showBoardModal === true`,`showBoardBlockModal === false`
|
||||
- Happy path:`chatStore.boardState?.status === 'completed'` → 点击"私董会"按钮 → `showBoardModal === true`(已完成私董会不阻塞)
|
||||
- Happy path:`chatStore.boardState?.status === 'dissolved'` → 同上
|
||||
- 拦截 path:`chatStore.boardState?.status === 'discussing'` → 点击"私董会"按钮 → `showBoardModal === false`,`showBoardBlockModal === true`
|
||||
- 拦截 path:`chatStore.boardState?.status === 'concluding'` → 同上
|
||||
- "新建会话"按钮:点击后 `showBoardBlockModal === false`,`chatStore.currentConversationId` 已变化,`chatStore.boardState === null`(新会话清空 boardState)
|
||||
- "我知道了"按钮:点击后 `showBoardBlockModal === false`,`chatStore.currentConversationId` 不变,`showBoardModal === false`
|
||||
|
||||
> **注**:Pinia setup store 的 ref 自动解包,`chatStore.boardState` 已是 `BoardState | null`,不能写 `chatStore.boardState.value`。如需 ref 访问用 `storeToRefs(chatStore).boardState.value`。
|
||||
|
||||
**Verification:**
|
||||
- 启动 Tauri 客户端,在已有私董会的会话中点击"私董会"按钮,应弹出提示而非 modal
|
||||
- 点击"新建会话"后自动跳转到新会话,boardState 清空
|
||||
- 在新会话中点击"私董会"按钮可正常打开 BoardMeetingModal
|
||||
|
||||
---
|
||||
|
||||
### U2. BoardBannerCard 简化为单行标题+副标题
|
||||
|
||||
**Goal:** 将 `BoardBannerCard.vue` 从带边框/紫条/进度条/专家 chip 的重样式简化为纯文本两行:`私董会 — {topic}` + `轮次:第 N/M 轮`,删除 BankOutlined 图标、4px 紫条、专家 chip、卡片背景/边框/圆角/阴影。
|
||||
|
||||
**Requirements:** R5, R6, R7, R8
|
||||
|
||||
**Dependencies:** 无(与 U1/U3 互不依赖,可并行)
|
||||
|
||||
**Files:**
|
||||
- src/agentkit/server/frontend/src/components/chat/messages/BoardBannerCard.vue(修改)
|
||||
- src/agentkit/server/frontend/src/components/chat/helpers/useMessageRenderer.ts(修改,**F3-A 决策**:删除 board_banner case 中 experts prop 传参,避免 Vue 3 attribute fallthrough 把 `[object Object]` 渲染到根 div)
|
||||
- src/agentkit/server/frontend/src/components/preview/scenes/Scene4BoardDiscussion.vue(修改,适配新 props)
|
||||
- src/agentkit/server/frontend/src/components/chat/messages/\_\_tests\_\_/BoardBannerCard.test.ts(新建)
|
||||
|
||||
**Approach:**
|
||||
- `<template>` 简化为:
|
||||
```vue
|
||||
<div class="board-banner-card">
|
||||
<div class="board-banner-card__title">私董会 — {{ topic }}</div>
|
||||
<div class="board-banner-card__meta">轮次:第 {{ currentRound }} / {{ maxRounds }} 轮</div>
|
||||
</div>
|
||||
```
|
||||
- 删除 `<script setup>` 中的 `BankOutlined` import 和 `progressPercent` computed
|
||||
- `interface Props` 删除 `experts` 字段,保留 `topic / maxRounds / currentRound`
|
||||
- `<style scoped>` 删除 `.board-banner-card__bar / __body / __icon / __experts / __chip / __chip--moderator / __chip-avatar / __progress / __progress-fill` 共 9 个类,仅保留:
|
||||
- `.board-banner-card` 容器(`margin-bottom: var(--space-3)` + `padding: var(--space-2) 0`,无 background/border/shadow)
|
||||
- `.board-banner-card__title`(`font-size: var(--font-md); font-weight: var(--font-weight-semibold); color: var(--text-primary); margin-bottom: var(--space-1)`)
|
||||
- `.board-banner-card__meta`(`font-size: var(--font-xs); color: var(--text-tertiary)`)
|
||||
- `Scene4BoardDiscussion.vue` 中调用 `<BoardBannerCard>` 的地方删除 `:experts` prop 传参
|
||||
- `useMessageRenderer.ts` 中 `board_banner` case 删除 `experts` prop 传参(**F3-A 决策**)
|
||||
|
||||
**Patterns to follow:**
|
||||
- `MessageShell.vue:150-163` 的 `.message-shell__name / __meta` 简洁文本样式
|
||||
|
||||
**Test scenarios:**
|
||||
- Happy path:传入 `topic="测试主题" maxRounds=5 currentRound=2` → 渲染两行文本,第一行"私董会 — 测试主题",第二行"轮次:第 2 / 5 轮"
|
||||
- 边界:`currentRound=0` → 第二行"轮次:第 0 / 5 轮"(不报错)
|
||||
- 边界:`maxRounds=0` → 第二行"轮次:第 1 / 0 轮"(不报错,不做除零校验)
|
||||
- 渲染契约:组件根 div **不**含 `background / border / border-radius / box-shadow` 任一 CSS 属性
|
||||
- 渲染契约:组件**不**渲染 `<BankOutlined />` / `.board-banner-card__bar` / `.board-banner-card__chip` 任一元素
|
||||
- Props 契约:传入 `experts` 数组时组件不报错(向后兼容,prop 已删除会被 Vue 忽略为 fallthrough attr,但需确认不会作为 attribute 渲染到根 div)
|
||||
- **F3-A fallthrough 契约**:`useMessageRenderer.ts` 的 `board_banner` case 不再传 `experts` prop(已删除),根 div **不**含 `experts="[object Object]"` 属性
|
||||
|
||||
**Verification:**
|
||||
- 启动 Tauri 客户端,发起私董会,第一条消息应显示为纯文本两行,无卡片背景/图标/进度条
|
||||
- 预览场景 `Scene4BoardDiscussion.vue` 仍然能正常渲染
|
||||
|
||||
---
|
||||
|
||||
### U3. MessageShell 浅灰气泡(方案B)
|
||||
|
||||
**Goal:** 在 `MessageShell.vue` 的 `.message-shell__content` 上增加方案 B 风格的浅灰圆角矩形气泡样式,仅 `role === 'assistant'` 时生效,使用 CSS 变量绑定,dark mode 自动切换。**F1-A 决策**:气泡用独立 token `--bg-message-bubble`(与 inline code/table 的 `--bg-secondary` 解耦);**F4-A 决策**:排除 `board_conclusion` 类型;**D4-方案1 决策**:空内容通过 `:empty` 隐藏气泡。
|
||||
|
||||
**Requirements:** R9, R10, R11, R12, R13, R14, R15
|
||||
|
||||
**Dependencies:** 无(与 U1/U2 互不依赖,可并行)
|
||||
|
||||
**Files:**
|
||||
- src/agentkit/server/frontend/src/styles/tokens.css(修改,**F1-A 决策**:新增 `--bg-message-bubble` token,light `#ffffff` / dark `#1f1f1f`)
|
||||
- src/agentkit/server/frontend/src/components/chat/messages/MessageShell.vue(修改)
|
||||
- src/agentkit/server/frontend/src/components/chat/messages/\_\_tests\_\_/MessageShell.test.ts(新建或扩展)
|
||||
|
||||
**Approach:**
|
||||
- **第一步(F1-A)**:在 `tokens.css` 的 `:root`(light)和 `[data-theme="dark"]`(或等效 dark 选择器)下新增 `--bg-message-bubble: #ffffff`(light)/ `#1f1f1f`(dark)。位置紧邻 `--bg-secondary` 定义,注释说明"消息气泡背景,与 inline code/table 背景解耦"
|
||||
- **第二步**:在 `MessageShell.vue` 的 `<style scoped>` 中新增规则:
|
||||
```css
|
||||
.message-shell--assistant .message-shell__content {
|
||||
background: var(--bg-message-bubble);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
/* F4-A: board_conclusion 类型不加气泡,保留 BoardConclusionCard 自身 card chrome */
|
||||
.message-shell--assistant.message-shell--conclusion .message-shell__content,
|
||||
.message-shell--assistant:has(.board-conclusion-card) .message-shell__content {
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
}
|
||||
/* D4-方案1: 空 slot 内容隐藏气泡背景,仅显示 thinking dots */
|
||||
.message-shell--assistant .message-shell__content:empty {
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
```
|
||||
- **第三步(F4-A 配套)**:`MessageShell.vue` 的 `interface Props` 新增 `messageType?: string` prop(可选),template 根 div 根据 `messageType === 'board_conclusion'` 添加 `.message-shell--conclusion` class。或使用 `:has(.board-conclusion-card)` 选择器(更简洁但需确认浏览器支持——现代浏览器已支持 `:has()`,Tauri WebView 基于 Chromium 111+ 支持)
|
||||
- 选择器限定 `.message-shell--assistant` 前缀,确保 `role === 'user'` 的 `UserBubble` 不受影响
|
||||
- 不使用 `!important`,让 `AssistantText.vue:257-401` 内部的 `pre / hljs / code / table` 样式自然继承(这些样式已用 `--code-bg` 等 token,浅灰气泡背景下自动适配)
|
||||
- 不修改 `.message-shell__content` 的 `display: flex / flex-direction: column / gap: var(--space-2) / width: 100% / max-width: 100%`(R13 要求)
|
||||
- 不修改 `.message-shell--user .message-shell__content` 的 `align-items: flex-end`(user 消息保持右对齐,不加气泡)
|
||||
|
||||
**Patterns to follow:**
|
||||
- `tokens.css:65,223` 的 `--bg-secondary` 定义(light `#fbfbfa`,dark `#1f1f1f`)—— `--bg-message-bubble` 紧邻此定义
|
||||
- `tokens.css:79,237` 的 `--border-color` 定义
|
||||
- `tokens.css:97` 的 `--radius-md: 6px`
|
||||
- `LoginView.vue:184-196` 的"显式绑定 token,禁硬编码"模式
|
||||
|
||||
**Test scenarios:**
|
||||
- Happy path(assistant):渲染 `<MessageShell role="assistant">` + 默认 slot 内容 → `.message-shell__content` 计算样式含 `background-color: rgb(255, 255, 255)`(light mode 下 `--bg-message-bubble: #ffffff`)
|
||||
- Happy path(user):渲染 `<MessageShell role="user">` + 默认 slot 内容 → `.message-shell__content` 计算样式 `background-color` 为空或 `rgba(0,0,0,0)`(不加气泡)
|
||||
- **F1-A 区分性**:slot 内含 `<code>async</code>` inline code → inline code 背景为 `--bg-secondary` (`#fbfbfa`),气泡背景为 `--bg-message-bubble` (`#ffffff`),二者视觉可区分
|
||||
- 气泡内代码块:slot 内含 `<pre><code>` → 代码块背景 `--code-bg` 不被气泡背景覆盖(视觉可区分)
|
||||
- 气泡宽度:`.message-shell__content` 计算样式 `width: 100%`,`max-width: 100%`(跟随消息列宽)
|
||||
- Dark mode:切换到 dark mode → `.message-shell__content` 计算样式 `background-color: rgb(31, 31, 31)`(`--bg-message-bubble: #1f1f1f`)
|
||||
- **F4-A 排除 conclusion**:渲染 `<MessageShell role="assistant" messageType="board_conclusion">` + `BoardConclusionCard` slot → `.message-shell__content` 计算样式 `background-color: transparent`,`border: none`(不加气泡,`BoardConclusionCard` 自身 card chrome 保留)
|
||||
- **D4-方案1 空内容隐藏**:渲染 `<MessageShell role="assistant">` + 空 slot → `.message-shell__content` 计算样式 `background-color: transparent`,`border: none`,`padding: 0`(`:empty` 选择器生效)
|
||||
- **D4-方案1 有内容恢复**:渲染 `<MessageShell role="assistant">` + 非空 slot → `:empty` 不匹配,气泡样式正常应用
|
||||
- 渲染契约:样式**不**使用 `!important`
|
||||
- 渲染契约:样式**不**硬编码 `#f3f4f6 / #fbfbfa / #ededec / #ffffff` 等值(必须用 CSS 变量)
|
||||
|
||||
**Verification:**
|
||||
- 启动 Tauri 客户端,发起普通 chat,assistant 消息应有浅灰(实际为 `#ffffff` 纯白,与 `#fbfbfa` 的 inline code 区分)圆角矩形气泡
|
||||
- 发起私董会,专家发言(board_speech)应有同款气泡
|
||||
- 发起私董会,结论消息(board_conclusion)应保留 `BoardConclusionCard` 自身 card 样式,**不**被气泡包裹
|
||||
- 切换 dark mode,气泡背景应自动变为深色(`#1f1f1f`)
|
||||
- user 消息(右侧)应显示为 demo 中的深色气泡(`--color-primary` 背景 + `--text-inverse` 白字),@board/@team 命令卡片保持现有浅色背景(见 U4)
|
||||
- assistant 消息在 thinking 阶段(空内容)不应显示空气泡矩形,仅显示三点动画
|
||||
|
||||
---
|
||||
|
||||
### U4. UserBubble 普通文本深色气泡(参考 demo)
|
||||
|
||||
**Goal:** 将 `UserBubble.vue` 的普通文本消息(`<span class="user-bubble__text">`)样式改为 demo 中的深色右对齐气泡:`--color-primary` 背景 + `--text-inverse` 白字 + `padding: var(--space-3) var(--space-4)` + `max-width: 70%`。@board/@team 命令卡片和文件附件保持现有 `--bg-tertiary` 浅色背景,不变。
|
||||
|
||||
**Requirements:** R16, R17, R18, R19
|
||||
|
||||
**Dependencies:** 无(与 U1/U2/U3 互不依赖,可并行)
|
||||
|
||||
**Files:**
|
||||
- src/agentkit/server/frontend/src/components/chat/messages/UserBubble.vue(修改)
|
||||
- src/agentkit/server/frontend/src/components/chat/messages/\_\_tests\_\_/UserBubble.test.ts(新建或扩展)
|
||||
|
||||
**Approach:**
|
||||
- `<script setup>` 新增 `isPlainText` computed:`!fileAttachment.value && !commandBubble.value`
|
||||
- `<template>` 根 div 的 `:class` 新增 `'user-bubble--text': isPlainText` modifier:
|
||||
```vue
|
||||
<div
|
||||
class="user-bubble"
|
||||
:class="{
|
||||
'user-bubble--focusable': msgId,
|
||||
'user-bubble--text': isPlainText,
|
||||
}"
|
||||
...
|
||||
>
|
||||
```
|
||||
- `<style scoped>` 新增 `.user-bubble--text` 规则(仅普通文本消息时覆盖 `.user-bubble` 默认背景):
|
||||
```css
|
||||
/* U4: 普通文本消息参考 demo 深色气泡,command card / file attachment 保持浅色 */
|
||||
.user-bubble--text {
|
||||
background: var(--color-primary);
|
||||
color: var(--text-inverse);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
max-width: 70%;
|
||||
}
|
||||
```
|
||||
- 不修改 `.user-bubble` 默认样式(`--bg-tertiary` 背景 + `--text-primary` 文字),让 command card 和 file attachment 继承现有浅色背景
|
||||
- 不修改 `.user-bubble__command` / `.user-bubble__command-*` 内部样式(保持现有结构化卡片可读性)
|
||||
- 不修改 `.user-bubble__actions` 操作按钮工具栏样式(当前 `--bg-secondary` 背景 + `--border-color` 边框,在深色气泡旁可见性需验证)
|
||||
- `--color-primary` 和 `--text-inverse` 在 light/dark mode 下自动反转(tokens.css:18,176 + 75,233 确认),dark mode 下 user 气泡自动变为浅色背景 + 深色文字
|
||||
|
||||
**Patterns to follow:**
|
||||
- demo `tmp_scheme_b_options_demo.html:230-240` 的 `.user-msg__bubble` 样式(`--color-primary` + `--text-inverse` + `--space-3 var(--space-4)` + `--radius-lg` + `max-width: 70%`)
|
||||
- `tokens.css:18,176` 的 `--color-primary` 定义(light `#1a1a1a` / dark `#fbfbfa`)
|
||||
- `tokens.css:75,233` 的 `--text-inverse` 定义(light `#ffffff` / dark `#1a1a1a`)
|
||||
|
||||
**Test scenarios:**
|
||||
- Happy path(普通文本):渲染 `<UserBubble content="Hello">` → 根 div 含 `.user-bubble--text` class,计算样式 `background-color: rgb(26, 26, 26)`(light mode 下 `--color-primary: #1a1a1a`),`color: rgb(255, 255, 255)`(`--text-inverse: #ffffff`)
|
||||
- Happy path(@board 命令):渲染 `<UserBubble content="@board 测试主题">` → 根 div **不**含 `.user-bubble--text` class,背景保持 `--bg-tertiary`(浅色),command card 内部样式不变
|
||||
- Happy path(@team 命令):渲染 `<UserBubble content="@team 测试">` → 同上,保持浅色背景
|
||||
- Happy path(文件附件):渲染 `<UserBubble content="[文件][x.pdf](url)">` → 根 div **不**含 `.user-bubble--text` class,背景保持 `--bg-tertiary`
|
||||
- Dark mode:切换到 dark mode → 普通文本消息 `.user-bubble--text` 计算样式 `background-color: rgb(251, 251, 250)`(`--color-primary: #fbfbfa`),`color: rgb(26, 26, 26)`(`--text-inverse: #1a1a1a`),自动反转
|
||||
- 操作按钮工具栏:普通文本消息 hover → `.user-bubble__actions` 可见,在深色气泡左侧(`right: calc(100% + var(--space-2))`)显示,`--bg-secondary` 背景与深色气泡有视觉区分
|
||||
- max-width 契约:普通文本消息 `max-width: 70%`,command card 保持 `max-width: 100%`(`.user-bubble__command` 自身设置)
|
||||
- 渲染契约:样式**不**使用 `!important`
|
||||
- 渲染契约:样式**不**硬编码 `#1a1a1a / #ffffff` 等值(必须用 CSS 变量)
|
||||
|
||||
**Verification:**
|
||||
- 启动 Tauri 客户端,发送普通文本消息 → 右侧显示深色圆角气泡,白字
|
||||
- 发送 @board 命令 → 右侧显示浅色结构化命令卡片(保持现有样式)
|
||||
- 发送 @team 命令 → 同上,保持浅色
|
||||
- 切换 dark mode → 普通文本消息自动反转为浅色气泡 + 深色文字,command card 不变
|
||||
- 普通文本消息 hover → 操作按钮工具栏在气泡左侧可见
|
||||
|
||||
---
|
||||
|
||||
## Scope Boundaries
|
||||
|
||||
### Deferred to Follow-Up Work
|
||||
|
||||
- **后端按会话去重 `board_started`**:本次只做前端拦截,不改后端。如果未来需要在多端/多浏览器同步场景下做强一致,再考虑后端校验(origin 文档已声明 deferred)。
|
||||
- **StickyModeHeader 顶部条的视觉细化**(徽章大小、专家头像间距、紫色边框粗细)不在本次范围。
|
||||
- **BoardConclusionCard / DebateBannerCard / TeamPlanCard 等其他 board 模式组件的样式统一**:本次只改 `BoardBannerCard`,其他卡片样式留待后续迭代。
|
||||
|
||||
### Outside this product's identity
|
||||
|
||||
- 不调整方案 B 调色板(`expertIdentity.ts` 的 12 色 PALETTE)和专家头像首字符规则。
|
||||
- 不调整私董会后端流程(`BoardOrchestrator` / `chatStream` 事件顺序)。
|
||||
- 不调整 AssistantText 的 markdown 渲染逻辑、代码高亮、表格样式。
|
||||
|
||||
## Risks & Dependencies
|
||||
|
||||
- **风险 1:U3 气泡样式可能影响 `AssistantText` 内部代码块/表格的视觉对比度** — 缓解:测试场景明确要求代码块背景 `--code-bg` 不被覆盖;`AssistantText.vue:257-401` 的 `pre/hljs/code/table` 样式已用独立 token,理论上不受影响。如出现对比度问题,可在 `AssistantText.vue` 内微调 `--code-bg` 但**不**回滚 U3 的气泡。
|
||||
- **风险 2:U1 的 `selectConversation(newId, true)` 强制 reload 可能导致用户感知"卡顿"** — 缓解:`force=true` 仅在用户主动点击"新建会话"时触发,是用户预期行为;如确实卡顿,可考虑后续优化(不在本次范围)。
|
||||
- **依赖 1:`chatStore.createConversation()` 是同步函数(无返回值),但内部已自动设置 `currentConversationId`** — 实现时需注意:先 `createConversation()` 再读 `chatStore.currentConversationId` 拿到新 id,不能直接 `const newId = await chatStore.createConversation()`。
|
||||
- **依赖 2:`BoardState.status` 当前联合类型为 4 个值,未来若新增 `forming / executing / synthesizing` 等中间状态需扩展"已存在"判断** — 在 `handleBoardClick` 中用 `if (status === 'discussing' || status === 'concluding')` 显式判断,未来扩展时只需补 `||`。
|
||||
|
||||
## Open Questions
|
||||
|
||||
以下问题在 ce-doc-review 中由 reviewer 提出。**Round 1(2026-07-02 demo 确认)** 已决策 F1/F3/F4/C2/D4,**Round 2(2026-07-02 复审)** 新增 7 个 findings(G1-G5 + D2-R2/D3-R2/D4-R2)全部 append 到本 section 留待实现期处理。其中 **G1 挑战 Round 1 D4-方案1 决策**(P0 load-bearing,3 reviewer 一致 confidence 100 认为不可实现),**G5 挑战 Round 1 F1-A 决策**(`#ffffff` 与 `#fbfbfa` 视觉不可区分)—— 实现期需优先处理这两个挑战。
|
||||
|
||||
### 已决策(Round 1 — 2026-07-02 demo 确认)
|
||||
|
||||
- **F1(feasibility,P1)— 已决策 F1-A**:U3 气泡背景与 inline code/table 背景冲突。**决策**:引入独立 token `--bg-message-bubble`(light `#ffffff` / dark `#1f1f1f`)区分气泡背景与 inline code/table 背景(保持 `--bg-secondary: #fbfbfa`)。已固化到 R9、U3 Approach、Key Technical Decisions。**⚠️ Round 2 G5 挑战**:`#ffffff` 与 `#fbfbfa` 色差 <1% 低于 JND,且 `#ffffff` 与 `--bg-primary` 相同导致气泡仅靠边框可见——见下方 G5。
|
||||
- **F3(feasibility,P2)— 已决策 F3-A**:U2 删除 `BoardBannerCard` 的 `experts` prop 后 `useMessageRenderer.ts:145` 的 fallthrough 风险。**决策**:在 U2 Files 列表中加 `useMessageRenderer.ts`,删除 `board_banner` case 中 `experts` prop 传参。已固化到 U2 Files、U2 Approach、U2 Test scenarios。
|
||||
- **F4(feasibility,P2)— 已决策 F4-A**:U3 气泡包裹 `BoardConclusionCard` 的嵌套冲突。**决策**:气泡选择器排除 `board_conclusion` 类型,`BoardConclusionCard` 保留自身 card chrome 不被气泡包裹。已固化到 R14、U3 Approach、U3 Test scenarios、Key Technical Decisions。**⚠️ Round 2 G2/G3 挑战**:`:has()` 事实错误 + `messageType` prop 选项需 ChatMessage.vue(不在 Files)——见下方 G2/G3。
|
||||
- **C2(coherence,P2)— 已应用 safe_auto**:R7 表述已修订为"删除 `.board-banner-card` 的重样式(background/border/border-radius/box-shadow)及 `__bar / __chip` 等重样式类,保留 `.board-banner-card` 容器(仅 margin/padding)+ `__title / __meta` 最小样式"。
|
||||
- **D4(design-lens,P2)— 已决策 D4-方案1**:U3 空内容气泡表现。**决策**:通过 `:empty` 选择器隐藏 `background / border / padding`,仅显示 thinking dots。已固化到 R15、U3 Approach、U3 Test scenarios、Key Technical Decisions。**⚠️ Round 2 G1 推翻**:`:empty` 永不匹配(AssistantText 总渲染根 div)——见下方 G1,实现期必须替换方案。
|
||||
|
||||
### 仍待实现期决定
|
||||
|
||||
#### Round 1 遗留
|
||||
|
||||
- **D1(design-lens,P1)**:U1 `selectConversation(newId, true)` 失败时无错误处理——modal 已关闭、currentConversationId 已变更、用户无反馈。待选方案:try/catch + a-message error toast + 回滚 currentConversationId。
|
||||
- **D2(design-lens,P2)**:U1 modal 关闭与 selectConversation resolve 之间的过渡期无 loading 反馈。待选方案:modal 内"新建会话"按钮 loading spinner / 全局 loading overlay。
|
||||
- **D3(design-lens,P2)**:U2 长 topic 文本无 overflow/wrap/truncation 策略。待选方案:`text-overflow: ellipsis` + `white-space: nowrap` / `word-break: break-word` / 多行 line-clamp。
|
||||
|
||||
#### Round 2 复审新增(2026-07-02)
|
||||
|
||||
**P0/P1 load-bearing(挑战 Round 1 决策,实现期优先处理):**
|
||||
|
||||
- **G1(feasibility + design-lens + adversarial 三方共识,P0,conf 100×3)— 推翻 Round 1 D4-方案1**:`:empty` 选择器永不匹配。`AssistantText.vue:1-6` 总是渲染根 `<div class="assistant-text">`(含 loading spinner / tool cards / routing div),`useMessageRenderer.ts:361-362` 总是将 AssistantText 作为 slot 子组件挂载到 `.message-shell__content`,所以 `.message-shell__content` 在生产环境永远非空。D4-方案1 的 `:empty` CSS 规则不会生效,thinking 阶段会显示空气泡矩形(正是 D4-方案1 要防止的 UX 缺陷)。plan 的测试场景"空 slot → `:empty` 生效"用真空 slot 不反映生产。**待选方案**:
|
||||
- (a) MessageShell 加 `isEmpty` computed(`!message.content && !message.thinking && toolCalls.length === 0`)+ `.message-shell--empty` class 覆盖气泡样式 —— 推荐,最简单
|
||||
- (b) 用 `:has(.assistant-text__loading:only-child)` 检测纯 loading 状态 —— 脆弱,不推荐
|
||||
- (c) AssistantText 真空时不渲染根 div —— 高风险,可能破坏 ThinkingBlock / ToolCallCard
|
||||
- 实现期需选择 (a)/(b)/(c) 并更新 R15、U3 Approach 第二步 CSS、U3 Test scenarios、Key Technical Decisions。
|
||||
|
||||
- **G5(adversarial + feasibility residual,P2,conf 75)— 挑战 Round 1 F1-A**:`#ffffff`(纯白)vs `#fbfbfa`(off-white)色差 <1% 低于人眼 JND(~2.3),且 `#ffffff` 与 `--bg-primary: #ffffff`(页面背景)相同导致气泡仅靠 1px `--border-color: #ededec` 边框可见。AE6"视觉可区分"断言可能不成立。origin 文档描述为"浅灰圆角矩形气泡",但 `#ffffff` 实际是白色。**待选方案**:
|
||||
- (a) `--bg-message-bubble` light 改为 `#f7f7f5`(实际浅灰,与 `--bg-tertiary` 一致)—— 视觉符合 origin 描述
|
||||
- (b) 保持 `#ffffff` 但修订 AE6 + U3 Test scenarios "视觉可区分" 为"token 级解耦(未来兼容),视觉差异 sub-JND"
|
||||
- (c) 改用 `#f3f4f6` 等更明显灰值
|
||||
- 实现期需选择 (a)/(b)/(c) 并更新 R9、U3 Approach、AE6、tokens.css 值。
|
||||
|
||||
**P2 gated_auto(事实修正 + 决策落地):**
|
||||
|
||||
- **G2(adversarial + feasibility residual,P2,conf 75)— Tauri `:has()` 事实错误**:plan U3 Approach 第 202 行称"Tauri WebView 基于 Chromium 111+ 支持",实际 Tauri 2.x macOS 用 WKWebView(Safari 引擎),Linux 用 WebKitGTK,仅 Windows 用 WebView2(Chromium)。`:has()` 在 Safari 15.4+(2022-03)/ WebKitGTK 2.35+ / Chromium 105+ 支持,实际可用但 plan 推理错误。**Fix**:修正 U3 Approach 第 202 行 + Key Technical Decisions F4-A 段为"Tauri 2.x macOS 用 WKWebView(Safari 15.4+ 支持 `:has()`);Linux WebKitGTK 2.35+;Windows WebView2 Chromium 105+。若需支持更老 macOS,优先用 `messageType` prop 方案"。
|
||||
|
||||
- **G3(coherence + adversarial,P2,conf 75)— F4-A `:has()` vs `messageType` prop 决策悬而未决 + ChatMessage.vue 缺失**:plan 对 F4-A 结论排除提供两个选项(`:has(.board-conclusion-card)` 选择器 OR `messageType` prop + `.message-shell--conclusion` class)但不决策;且三处描述不一致(Key Technical Decisions 用 `:not(:has())`,U3 Approach 用"或",U3 CSS 用两者 comma-joined)。若选 `messageType` prop,需修改 ChatMessage.vue 传递 `:message-type="spec.type"`,但 ChatMessage.vue 不在 U3 Files 列表。**Fix**:实现期决策用 `messageType` prop(更稳健,无 CSS 兼容风险),添加 `src/agentkit/server/frontend/src/components/chat/ChatMessage.vue` 到 U3 Files,更新 Key Technical Decisions + U3 Approach + U3 CSS 三处统一为 `messageType` prop 方案。
|
||||
|
||||
**P2 manual(设计决策):**
|
||||
|
||||
- **D2-R2(design-lens,P2,conf 75)— U4 focus ring 在深色气泡上不可见**:`UserBubble.vue:304` 用 `var(--accent-primary, #1a1a1a)`,但 `--accent-primary` 在 tokens.css 未定义,fallback `#1a1a1a` 与 `--color-primary` 相同。U4 深色气泡(`--color-primary: #1a1a1a` 背景)上 focus ring 不可见,WCAG 1.4.11 违规。**待选方案**:(a) 定义 `--accent-primary` token(如 `#3b82f6` light / `#60a5fa` dark);(b) 改用 `--color-info` / `--accent-team`;(c) 不在本次处理(pre-existing bug,U4 加剧但不引入)。
|
||||
|
||||
- **D3-R2(design-lens,P2,conf 75)— U3 dark mode 代码块对比度不足**:dark mode 下 `--code-bg: #11111b`(tokens.css:255)vs `--bg-message-bubble: #1f1f1f` 仅差 ~14 亮度级,代码块可能融入气泡。light mode 对比度足够(`#1e1e2e` on `#ffffff`)。**待选方案**:(a) 加 dark mode 专属测试场景验证对比度;(b) 调整 `--bg-message-bubble` dark 值为 `#252525`(接近 `--bg-elevated`);(c) 给 `pre` 加 `border: 1px solid var(--border-color)`。
|
||||
|
||||
- **D4-R2(design-lens,P2,conf 75)— U2 简化 banner 缺乏视觉分隔**:删除所有 chrome 后,两行纯文本 banner(font-md semibold + font-xs tertiary)可能融入消息流,失去"section divider"作用。BankOutlined 图标删除后无视觉锚点。**待选方案**:(a) 保留 2px 左边框 `--accent-board`(匹配私董会身份,克制);(b) 加小色点前缀;(c) 纯排版足够("私董会 —" 文本前缀即锚点)。
|
||||
|
||||
## System-Wide Impact
|
||||
|
||||
- **前端用户**:所有 Tauri 客户端和 Web GUI 用户将看到 (a) 私董会按钮在已有私董会的会话中弹提示而非 modal;(b) 私董会开始消息显示为简洁两行文本;(c) 所有 assistant 消息有浅灰圆角气泡(`--bg-message-bubble: #ffffff`),board_conclusion 例外(保留自身 card);(d) thinking 阶段不显示空气泡。视觉变化明显但符合方案 B 整体取向。
|
||||
- **后端/运维**:无影响(不动后端、不动部署、不动配置)。
|
||||
- **其他团队**:无影响(仅前端 Vue 组件)。
|
||||
|
||||
## Acceptance Examples
|
||||
|
||||
- AE1:用户在已存在 `status='discussing'` 私董会的会话中点击"私董会"按钮 → 弹出 a-modal 提示"当前会话已存在私董会",点击"新建会话"后自动跳转到新空会话,boardState 清空。
|
||||
- AE2:用户在新会话中发起私董会 → 第一条消息显示为两行纯文本"私董会 — {topic}" + "轮次:第 1/5 轮",无卡片背景/图标/进度条。
|
||||
- AE3:用户发起私董会,专家依次发言 → 每条专家发言(board_speech)显示为:彩色圆形头像 + 粗体名字 + "第 N 轮 · 专家" + 浅灰圆角矩形气泡包裹内容。
|
||||
- AE4:用户发起普通 chat(不涉及私董会)→ assistant 消息有浅灰圆角气泡,user 普通文本消息有深色右对齐气泡(U4)。
|
||||
- AE5:用户切换到 dark mode → 浅灰气泡自动变为深色(`--bg-message-bubble: #1f1f1f`),代码块/表格对比度保持可读。
|
||||
- AE6(F1-A):assistant 消息气泡内含 inline code → inline code 背景 `--bg-secondary: #fbfbfa` 与气泡背景 `--bg-message-bubble: #ffffff` 视觉可区分。
|
||||
- AE7(F4-A):私董会结论消息(board_conclusion)→ `BoardConclusionCard` 保留自身 card chrome(background/border/shadow),**不**被气泡包裹,无嵌套冲突。
|
||||
- AE8(D4-方案1):assistant 消息在 thinking 阶段(空内容)→ 不显示空气泡矩形,仅显示三点动画;内容流入后自动出现气泡。
|
||||
- AE9(U4):用户发送普通文本消息 → 右侧显示深色圆角气泡(`--color-primary` 背景 + 白字),max-width 70%
|
||||
- AE10(U4):用户发送 @board 命令 → 右侧显示浅色结构化命令卡片(保持现有 `--bg-tertiary` 背景),不受深色气泡影响
|
||||
- AE11(U4):切换 dark mode → 普通文本消息自动反转为浅色气泡 + 深色文字,@board 命令卡片样式不变
|
||||
|
|
@ -2,13 +2,39 @@
|
|||
date: 2026-07-02
|
||||
type: fix
|
||||
title: 修复私董会 transient state 残留 + ReAct 工具调用引导不足
|
||||
status: ready
|
||||
status: in-progress
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
收尾两个独立 bug:(1) 前端 store-level transient state(`boardState` / `debateState` / `collaborationState`)在 `createConversation` / `selectConversation` / `deleteConversation` 三个动作下的重置口径不一致,导致新建对话后私董会顶部标题残留、跨会话状态泄漏;(2) ReAct 引擎 `_build_tool_use_prompt` 规则 3 "如果不需要工具就能回答,直接回答即可" 给 LLM 留出偷懒窗口,且工具调用提示被后置的 tool section 覆盖,导致复杂需求(涉及外部数据 / 多步分析)下 LLM 倾向于直接回答而非调用 `web_search` / `baidu_search`。Bug 1 覆盖前端 3 个 action 路径对称重置;Bug 2 仅做 L0(提示规则调整),L1(工具描述扩展)与 L2(PLAN_EXEC 启用)按用户决策拆为独立 plan。
|
||||
|
||||
## Progress
|
||||
|
||||
| Unit | 状态 | 验证 | 提交 |
|
||||
|------|------|------|------|
|
||||
| U1 createConversation 重置 | done | 5 前端单测 pass | 7376005 |
|
||||
| U2 selectConversation 条件重置 | done | 5 前端单测 pass | 7376005 |
|
||||
| U3 deleteConversation 补全 | done | 5 前端单测 pass | 7376005 |
|
||||
| U4 ReAct prompt 规则重排 | done | 6 后端单测 pass | 7376005 |
|
||||
| U5 端到端验证测试 | done | 11 单测全 pass | 7376005 |
|
||||
| U6 Bug 2 L4 真实 LLM smoke test | done | 3/4 tool-call + 1/1 direct pass | (本 commit) |
|
||||
| U7 工作树未提交变更清理 | done | git status 干净 + vitest 138/139 pass | 9e2ccf5..44f4f1c |
|
||||
|
||||
Bug 2 状态:L4 verified(L0 规则重排在真实 LLM 调用下生效)
|
||||
|
||||
L4 smoke test 结果(2026-07-02,bailian-coding/qwen3.7-plus):
|
||||
- Probe #1 external_info: PASS(8 次 web_search 调用,99.9s)
|
||||
- Probe #2 realtime_data: ERROR(120s 超时,非 LLM 不调用工具)
|
||||
- Probe #3 multi_step: PASS(8 次 web_search 调用,62.6s)
|
||||
- Probe #4 realtime_data_simple: PASS(3 次 web_search 调用,23.8s)
|
||||
- Probe #5 no_tool_escape_hatch: PASS(0 次工具调用,直接回答,4.2s)
|
||||
- 判定:3/4 tool-call pass(达阈值 ≥3/4)+ 1/1 direct pass → L4 verified
|
||||
|
||||
PR: http://8.153.107.96/gitea/fischer/fischer-agentkit/pulls/17
|
||||
ce-code-review: mode:agent, 无 actionable findings
|
||||
ce-test-browser: agent-browser 已安装(U6 用脚本直接验证,未走前端)
|
||||
|
||||
## Problem Frame
|
||||
|
||||
**Bug 1:私董会顶部标题在新对话后残留**
|
||||
|
|
@ -240,6 +266,69 @@ status: ready
|
|||
|
||||
**Verification:** `cd src/agentkit/server/frontend && npm run test:unit` + `pytest tests/unit/test_react_engine.py` 全部通过
|
||||
|
||||
### U6. Bug 2 L4 真实 LLM smoke test 验证
|
||||
|
||||
**Goal:** 验证 U4 的 L0 规则重排在真实 LLM 调用中是否生效 — Agent 面对复杂需求(外部信息 / 实时数据 / 多步分析)时是否调用 `web_search` 而非直接回答。将 Bug 2 状态从 "hypothesis applied, pending L4 verification" 升级为 "verified" 或回退并触发 L1/L2。
|
||||
|
||||
**Requirements:** R4 验证闭环
|
||||
|
||||
**Dependencies:** U4(已 done),PR #17 合入后执行
|
||||
|
||||
**Files:**
|
||||
- tests/manual/test_react_l4_smoke.py(新建手动 smoke test 脚本,不进 CI)
|
||||
|
||||
**Approach:**
|
||||
- 准备 5 个 probe query,覆盖外部信息 / 实时数据 / 多步分析 / 不确定事实 / 混合类型:
|
||||
1. "收集 GitHub Trending 前 10 个项目信息并分析商业价值"(原 bug 复现 query)
|
||||
2. "最新 AI 领域有什么重要新闻?"(实时数据)
|
||||
3. "对比 React 和 Vue 3 在大型项目中的性能差异"(多步分析 + 外部信息)
|
||||
4. "今天上海天气怎么样?"(实时数据,简单)
|
||||
5. "请帮我总结这段文字:..."(无需工具,验证 escape hatch 规则 4 仍有效)
|
||||
- 对每个 query 跑 `ReActEngine.execute()`,记录是否触发 `web_search` tool call
|
||||
- query 1-4 期望触发工具调用(≥3/4 pass 算通过),query 5 期望不触发
|
||||
- 如通过率 < 3/4,触发 L1(工具描述扩展)并创建独立 plan
|
||||
- 使用 `agent-browser` 打开 http://localhost:15173 进行前端层验证(可选):在 chat 中输入 probe query,观察是否出现 tool_call step
|
||||
|
||||
**Test scenarios:**
|
||||
- probe query 1-4:ReAct 循环中至少出现一次 `web_search` tool call
|
||||
- probe query 5:ReAct 循环中无 tool call,直接回答
|
||||
- 回归断言:U4 单测仍 pass(L0 文本未变)
|
||||
|
||||
**Verification:** `python3 tests/manual/test_react_l4_smoke.py` 输出报告:5 个 query 的 tool call 统计 + pass/fail 判定。通过后更新本 plan Progress 表 U6 状态为 done,Bug 2 状态升级为 "L4 verified"
|
||||
|
||||
### U7. 工作树未提交变更清理
|
||||
|
||||
**Goal:** 清理工作树中 32 个来自前序 session 的未提交变更,按 concern 分组提交,使工作树恢复干净状态。避免后续开发时 diff 噪声干扰 review。
|
||||
|
||||
**Requirements:** 无(工程治理)
|
||||
|
||||
**Dependencies:** 无(可与 U6 并行)
|
||||
|
||||
**Files (按 concern 分组):**
|
||||
|
||||
| 分组 | 文件 | 来源 session | 建议提交方式 |
|
||||
|------|------|-------------|-------------|
|
||||
| A. expert avatar emoji 移除 | configs/experts/*.yaml (15 个) | emoji 移除 plan (2026-07-02-001) | 单独 commit `refactor: expert avatar 改为首字符` |
|
||||
| B. dev 环境配置 | docker-compose.yaml, scripts/dev-start.sh, docker-compose.dev.yml, .env.dev, src/agentkit/server/config.py | dev 环境修复 session | 单独 commit `fix: dev 环境配置 + 端口隔离` |
|
||||
| C. board 元数据持久化 | src/agentkit/experts/board_orchestrator.py, src/agentkit/server/routes/chat.py | 私董会持久化修复 session | 单独 commit `fix: board_speech/round_summary 持久化 avatar/color 元数据` |
|
||||
| D. 前端方案B + board UI | StickyModeHeader.vue, expertIdentity.ts, useMessageRenderer.ts, BoardRoundCard.vue, MessageShell.vue, Scene4BoardDiscussion.vue, chatStream.ts, types.ts, LoginView.vue | 方案B + 私董会限制 session | 需 review 后 commit,部分可能已通过 PR #15 合入 |
|
||||
| E. .understand-anything | .understand-anything/* (3 tracked + untracked) | knowledge graph 工具 | 加入 .gitignore 或单独 commit |
|
||||
| F. 未跟踪 plan/brainstorm 文档 | docs/brainstorms/2026-07-02-*.md, docs/plans/2026-07-02-001-*.md | ce-plan/ce-brainstorm 产物 | commit 为决策记录 |
|
||||
|
||||
**Approach:**
|
||||
- 对每组 `git diff HEAD -- <files>` 检查变更内容,确认无冲突
|
||||
- 分组 commit,每组 commit message 遵循 conventional commits 格式
|
||||
- D 组需特别注意:检查是否与已合入的 PR #15 内容重叠,避免重复提交
|
||||
- E 组(.understand-anything):建议加入 .gitignore 而非 commit(是工具生成的本地索引)
|
||||
- 如某组变更属于未完成的 feature(如方案B),则 stash 而非 commit,待对应 plan 完成后再提交
|
||||
|
||||
**Test scenarios:**
|
||||
- 每组 commit 后 `git status --short` 该组文件不再出现
|
||||
- 全部完成后 `git status --short` 仅显示 untracked(.understand-anything 等 gitignored 项)
|
||||
- `npm run typecheck` + `ruff check src/` 仍通过(确认无回归)
|
||||
|
||||
**Verification:** `git status --short` 输出为空(或仅 gitignored 项);`npm run test:unit` + `pytest tests/unit/ -x -q` 全套通过
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- **L1(工具描述扩展)**:web_search / baidu_search / web_crawl 工具 description 添加"何时使用"触发关键词(如"需要最新互联网信息、新闻、Trending、股价时使用 web_search")。按用户决策推迟为独立 plan
|
||||
|
|
@ -256,15 +345,17 @@ status: ready
|
|||
|
||||
## Deferred to Follow-Up Work
|
||||
|
||||
- L1:web_search / baidu_search 工具 description 扩展(独立 plan)
|
||||
- L2:启用 PLAN_EXEC phase policy 处理复杂需求(独立 plan)
|
||||
- L1:web_search / baidu_search 工具 description 扩展(独立 plan)— 仅在 U6 L4 smoke test 未通过时触发
|
||||
- L2:启用 PLAN_EXEC phase policy 处理复杂需求(独立 plan)— 仅在 U6 通过但工具调用率仍不理想时触发
|
||||
- 重构 stream-owned state 为按 conversationId 拆分(架构性,独立 plan)
|
||||
|
||||
## Verification (per unit, summary)
|
||||
|
||||
- U1/U2/U3:`cd src/agentkit/server/frontend && npm run test:unit` 全套通过;新增 3 个 `describe` 块共 8+ test cases
|
||||
- U4:`pytest tests/unit/test_react_engine.py -k ToolUsePromptRules` 通过;新增 1 个 test class 4-5 个 test cases。**Bug 2 状态声明**:L0 文本断言通过后 Bug 2 标记为 "hypothesis applied, pending L4 verification"(非 "fixed"),真实 LLM smoke test 在 L1/L2 独立 plan 中执行
|
||||
- U5:完整套件通过;端到端 4-5 个联动测试
|
||||
- U4:`pytest tests/unit/test_react_engine.py -k ToolUsePromptRules` 通过;新增 1 个 test class 4-5 个 test cases(已 done)。**Bug 2 状态声明**:L0 文本断言通过后 Bug 2 标记为 "hypothesis applied, pending L4 verification"(非 "fixed"),真实 LLM smoke test 在 U6 中执行
|
||||
- U5:完整套件通过;端到端 4-5 个联动测试(已 done,11 单测全 pass)
|
||||
- U6:`python3 tests/manual/test_react_l4_smoke.py` 输出 5 个 probe query 的 tool call 统计;≥3/4 外部信息 query 触发 web_search + query 5 不触发 → Bug 2 状态升级为 "L4 verified"
|
||||
- U7:`git status --short` 为空(或仅 gitignored 项);`npm run test:unit` + `pytest tests/unit/ -x -q` 全套通过
|
||||
- 集成:`python3 -m pytest tests/unit/ -x -q`(AGENTS.md 硬约束) + `cd src/agentkit/server/frontend && npm run test:unit` 通过
|
||||
- Lint:`ruff check src/ && ruff format src/`(AGENTS.md 硬约束)通过
|
||||
- TypeScript:`cd src/agentkit/server/frontend && npm run typecheck` 通过
|
||||
|
|
|
|||
|
|
@ -17,6 +17,14 @@
|
|||
# Python >= 3.11, Node.js >= 18, Redis, PostgreSQL (均自动检查)
|
||||
# --tauri 需要:Rust 工具链(rustup / brew install rust)
|
||||
#
|
||||
# 端口(与 .env.dev 保持一致):
|
||||
# 18001 — AgentKit 后端 API
|
||||
# 18002 — Web GUI(前端 + 内置静态服务)
|
||||
# 15173 — Vite 开发服务器(--tauri 模式)
|
||||
# 15174 — Vite HMR websocket
|
||||
# 6381 — Redis(agentkit 专属容器,避免与 pms-redis:6379 / geo_redis:6380 冲突)
|
||||
# 5435 — PostgreSQL+pgvector(agentkit 专属容器,避免与 geo_db:5433 冲突)
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
|
@ -49,12 +57,14 @@ Fischer AgentKit — 本地开发环境启动
|
|||
|
||||
模式说明:
|
||||
默认 Web 模式:agentkit gui (前后端 + 内置静态服务)
|
||||
--tauri Tauri 模式:后端 API + Vite (:5173) + Tauri 桌面窗口
|
||||
--tauri Tauri 模式:后端 API + Vite (:15173) + Tauri 桌面窗口
|
||||
|
||||
端口映射:
|
||||
8000 — 后端 API
|
||||
8002 — Web GUI / 前端静态服务
|
||||
5173 — Vite 开发服务器(--tauri 模式)
|
||||
18001 — 后端 API
|
||||
18002 — Web GUI / 前端静态服务
|
||||
15173 — Vite 开发服务器(--tauri 模式)
|
||||
6381 — Redis(agentkit 专属容器)
|
||||
5435 — PostgreSQL+pgvector(agentkit 专属容器)
|
||||
EOF
|
||||
}
|
||||
|
||||
|
|
@ -112,11 +122,11 @@ print_status() {
|
|||
echo -e "${CYAN}═══════════════════════════════════════════════════${NC}"
|
||||
echo -e "$([[ $S_DEPS -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_DEPS -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") 依赖检查"
|
||||
echo -e "$([[ $S_ENV -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_ENV -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") 环境配置"
|
||||
echo -e "$([[ $S_REDIS -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_REDIS -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") Redis"
|
||||
echo -e "$([[ $S_PG -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_PG -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") PostgreSQL"
|
||||
echo -e "$([[ $S_BACKEND -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_BACKEND -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") 后端服务 (:8000)"
|
||||
echo -e "$([[ $S_REDIS -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_REDIS -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") Redis (:6381)"
|
||||
echo -e "$([[ $S_PG -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_PG -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") PostgreSQL (:5435)"
|
||||
echo -e "$([[ $S_BACKEND -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_BACKEND -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") 后端服务 (:18001)"
|
||||
if [[ $MODE == "gui" || $MODE == "tauri" ]]; then
|
||||
echo -e "$([[ $S_FRONTEND -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_FRONTEND -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") 前端服务 (:8002)"
|
||||
echo -e "$([[ $S_FRONTEND -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_FRONTEND -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") 前端服务 (:18002)"
|
||||
fi
|
||||
if [[ $MODE == "tauri" ]]; then
|
||||
echo -e "$([[ $S_TAURI -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_TAURI -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") Tauri 客户端"
|
||||
|
|
@ -180,30 +190,23 @@ check_redis() {
|
|||
section "检查 Redis"
|
||||
set_status redis 1
|
||||
|
||||
if command -v redis-cli &>/dev/null && redis-cli ping 2>/dev/null | grep -q PONG; then
|
||||
ok "Redis 运行中"
|
||||
# agentkit 专属容器映射到 6381,避免与 pms-redis(6379) / geo_redis(6380) 冲突
|
||||
if redis-cli -h 127.0.0.1 -p 6381 ping 2>/dev/null | grep -q PONG; then
|
||||
ok "Redis 运行中 (127.0.0.1:6381, agentkit 专属容器)"
|
||||
set_status redis 2
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Docker 方式
|
||||
local name="fischer-redis-dev"
|
||||
if docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^${name}$"; then
|
||||
ok "Redis 容器运行中"
|
||||
set_status redis 2
|
||||
return 0
|
||||
fi
|
||||
|
||||
warn "Redis 未运行,尝试启动 Docker 容器..."
|
||||
if docker run -d --name "$name" -p 6379:6379 redis:7-alpine &>/dev/null; then
|
||||
warn "Redis 未运行 (6381),启动 agentkit 专属 Docker 容器..."
|
||||
if docker compose -f docker-compose.yaml -f docker-compose.dev.yml up -d redis &>/dev/null; then
|
||||
sleep 2
|
||||
if docker exec "$name" redis-cli ping 2>/dev/null | grep -q PONG; then
|
||||
ok "Redis Docker 容器启动成功 (:6379)"
|
||||
if redis-cli -h 127.0.0.1 -p 6381 ping 2>/dev/null | grep -q PONG; then
|
||||
ok "Redis Docker 容器启动成功 (127.0.0.1:6381)"
|
||||
set_status redis 2
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
fail "Redis 启动失败(请确保 Docker 运行中,或手动启动 Redis)"
|
||||
fail "Redis 启动失败(请确保 Docker 运行中)"
|
||||
set_status redis 3
|
||||
return 1
|
||||
}
|
||||
|
|
@ -212,30 +215,18 @@ check_postgres() {
|
|||
section "检查 PostgreSQL"
|
||||
set_status pg 1
|
||||
|
||||
if lsof -i :5432 2>/dev/null | grep -q LISTEN; then
|
||||
ok "PostgreSQL 已在 :5432 监听"
|
||||
# agentkit 专属容器映射到 5435,避免与 geo_db(5433) 冲突
|
||||
if PGPASSWORD=agentkit psql -h 127.0.0.1 -p 5435 -U agentkit -d agentkit -c "SELECT 1" &>/dev/null; then
|
||||
ok "PostgreSQL 运行中 (127.0.0.1:5435, agentkit 专属容器)"
|
||||
set_status pg 2
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Docker 方式
|
||||
local name="fischer-pg-dev"
|
||||
if docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^${name}$"; then
|
||||
ok "PostgreSQL Docker 容器运行中"
|
||||
set_status pg 2
|
||||
return 0
|
||||
fi
|
||||
|
||||
warn "PostgreSQL 未在 :5432 运行,尝试启动 Docker 容器..."
|
||||
if docker run -d --name "$name" \
|
||||
-p 5432:5432 \
|
||||
-e POSTGRES_USER=agentkit \
|
||||
-e POSTGRES_PASSWORD=agentkit \
|
||||
-e POSTGRES_DB=agentkit \
|
||||
pgvector/pgvector:pg15 &>/dev/null; then
|
||||
warn "PostgreSQL 未运行 (5435),启动 agentkit 专属 Docker 容器..."
|
||||
if docker compose -f docker-compose.yaml -f docker-compose.dev.yml up -d postgres &>/dev/null; then
|
||||
sleep 3
|
||||
if docker exec "$name" pg_isready -U agentkit &>/dev/null; then
|
||||
ok "PostgreSQL Docker 容器启动成功 (:5432)"
|
||||
if PGPASSWORD=agentkit psql -h 127.0.0.1 -p 5435 -U agentkit -d agentkit -c "SELECT 1" &>/dev/null; then
|
||||
ok "PostgreSQL Docker 容器启动成功 (127.0.0.1:5435)"
|
||||
set_status pg 2
|
||||
return 0
|
||||
fi
|
||||
|
|
@ -281,17 +272,17 @@ start_backend() {
|
|||
section "启动后端服务"
|
||||
set_status backend 1
|
||||
|
||||
info "启动后端 API (:8000)..."
|
||||
info "启动后端 API (:18001)..."
|
||||
source .venv/bin/activate
|
||||
agentkit serve --port 8000 &
|
||||
agentkit serve --port 18001 &
|
||||
BACKEND_PID=$!
|
||||
|
||||
# 等待健康检查就绪(最多 60 秒)
|
||||
info "等待后端就绪..."
|
||||
local attempt=0
|
||||
while [[ $attempt -lt 60 ]]; do
|
||||
if curl -sf http://127.0.0.1:8000/api/v1/health &>/dev/null; then
|
||||
ok "后端 API 就绪 (http://127.0.0.1:8000, PID $BACKEND_PID)"
|
||||
if curl -sf http://127.0.0.1:18001/api/v1/health &>/dev/null; then
|
||||
ok "后端 API 就绪 (http://127.0.0.1:18001, PID $BACKEND_PID)"
|
||||
set_status backend 2
|
||||
return 0
|
||||
fi
|
||||
|
|
@ -318,17 +309,17 @@ start_gui() {
|
|||
section "启动 Web GUI"
|
||||
set_status frontend 1
|
||||
|
||||
info "启动 Web GUI (:8002)..."
|
||||
info "启动 Web GUI (:18002)..."
|
||||
source .venv/bin/activate
|
||||
agentkit gui --port 8002 &
|
||||
agentkit gui --port 18002 &
|
||||
GUI_PID=$!
|
||||
|
||||
# 等待就绪
|
||||
info "等待 Web GUI 就绪..."
|
||||
local attempt=0
|
||||
while [[ $attempt -lt 60 ]]; do
|
||||
if curl -sf http://127.0.0.1:8002/api/v1/health &>/dev/null; then
|
||||
ok "Web GUI 就绪 (http://127.0.0.1:8002, PID $GUI_PID)"
|
||||
if curl -sf http://127.0.0.1:18002/api/v1/health &>/dev/null; then
|
||||
ok "Web GUI 就绪 (http://127.0.0.1:18002, PID $GUI_PID)"
|
||||
set_status frontend 2
|
||||
return 0
|
||||
fi
|
||||
|
|
@ -364,7 +355,7 @@ start_tauri() {
|
|||
fi
|
||||
|
||||
info "启动 Tauri 桌面客户端..."
|
||||
info " (Vite → :5173, 后端 API → :8000)"
|
||||
info " (Vite → :15173, 后端 API → :18001)"
|
||||
cd "$FE_DIR"
|
||||
npm run tauri dev &
|
||||
TAURI_PID=$!
|
||||
|
|
@ -400,7 +391,7 @@ stop_services() {
|
|||
echo ""
|
||||
info "正在停止所有服务..."
|
||||
|
||||
for port in 8000 8001 8002 5173; do
|
||||
for port in 18001 18002 15173 15174; do
|
||||
local pid=$(lsof -ti :$port 2>/dev/null || true)
|
||||
if [[ -n "$pid" ]]; then
|
||||
kill $pid 2>/dev/null && ok "端口 $port 已停止" || true
|
||||
|
|
@ -474,14 +465,14 @@ if [[ $FAILED -eq 0 ]]; then
|
|||
echo -e "${CYAN}═══════════════════════════════════════════════════${NC}"
|
||||
echo ""
|
||||
if [[ $MODE == "gui" ]]; then
|
||||
echo -e " Web GUI: ${GREEN}http://localhost:8002${NC}"
|
||||
echo " (在浏览器中打开,或直接在 http://localhost:8002 访问)"
|
||||
echo -e " Web GUI: ${GREEN}http://localhost:18002${NC}"
|
||||
echo " (在浏览器中打开,或直接在 http://localhost:18002 访问)"
|
||||
elif [[ $MODE == "tauri" ]]; then
|
||||
echo -e " 后端 API: ${GREEN}http://localhost:8000${NC}"
|
||||
echo -e " Vite 热重载: ${GREEN}http://localhost:5173${NC}"
|
||||
echo -e " 后端 API: ${GREEN}http://localhost:18001${NC}"
|
||||
echo -e " Vite 热重载: ${GREEN}http://localhost:15173${NC}"
|
||||
echo " Tauri 桌面窗口应已自动打开"
|
||||
elif [[ $MODE == "serve" ]]; then
|
||||
echo -e " 后端 API: ${GREEN}http://localhost:8000${NC}"
|
||||
echo -e " 后端 API: ${GREEN}http://localhost:18001${NC}"
|
||||
fi
|
||||
echo ""
|
||||
echo -e " ${YELLOW}按 Ctrl+C 停止所有服务${NC}"
|
||||
|
|
@ -491,7 +482,7 @@ else
|
|||
echo -e "${CYAN}═══════════════════════════════════════════════════${NC}"
|
||||
echo ""
|
||||
echo -e " 诊断命令:"
|
||||
echo -e " 查看日志: ${CYAN}curl http://127.0.0.1:8000/api/v1/health${NC}"
|
||||
echo -e " 查看日志: ${CYAN}curl http://127.0.0.1:18001/api/v1/health${NC}"
|
||||
echo -e " 停止服务: ${CYAN}bash scripts/dev-stop.sh${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
|
|
|||
|
|
@ -167,6 +167,8 @@ class BoardOrchestrator:
|
|||
"round_summary",
|
||||
{
|
||||
"moderator_name": moderator.config.name,
|
||||
"moderator_avatar": moderator.config.avatar,
|
||||
"moderator_color": moderator.config.color,
|
||||
"content": summary,
|
||||
"round": round_num,
|
||||
"continue": round_num < self._team.max_rounds,
|
||||
|
|
|
|||
|
|
@ -555,7 +555,11 @@ def load_dotenv(
|
|||
key, _, value = line.partition("=")
|
||||
key = key.strip()
|
||||
value = value.strip().strip("\"'")
|
||||
if not key or key in os.environ:
|
||||
# Skip only if key is set to a non-empty value in the environment.
|
||||
# An empty/whitespace-only value (e.g. from a shell template like
|
||||
# `${VAR:-}` that expanded to nothing) is treated as "not set" so
|
||||
# subsequent .env files can still provide a real value.
|
||||
if not key or (key in os.environ and os.environ[key].strip()):
|
||||
continue
|
||||
# Apply allowlist if provided
|
||||
if prefixes is not None or exact is not None:
|
||||
|
|
@ -579,8 +583,10 @@ def load_config_with_dotenv(config_path: str | Path) -> ServerConfig:
|
|||
This is the canonical way to load config in all CLI commands and app factory.
|
||||
"""
|
||||
config_path = str(config_path)
|
||||
dotenv = Path(config_path).parent / ".env"
|
||||
load_dotenv(dotenv)
|
||||
config_dir = Path(config_path).parent
|
||||
# Load .env, .env.dev, .env.local in order (first non-empty value wins).
|
||||
for candidate in (".env", ".env.dev", ".env.local"):
|
||||
load_dotenv(config_dir / candidate)
|
||||
return ServerConfig.from_yaml(config_path)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -346,6 +346,8 @@ export interface IExpertSpeechData {
|
|||
/** round_summary event payload */
|
||||
export interface IRoundSummaryData {
|
||||
moderator_name: string;
|
||||
moderator_avatar?: string;
|
||||
moderator_color?: string;
|
||||
content: string;
|
||||
round: number;
|
||||
continue: boolean;
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@
|
|||
class="sticky-mode-header__avatar"
|
||||
:class="{ 'sticky-mode-header__avatar--lead': expert.isLead }"
|
||||
type="button"
|
||||
:style="{ borderColor: expert.color }"
|
||||
:style="{ background: expert.color, color: 'var(--text-inverse)', borderColor: expert.color }"
|
||||
:aria-label="`查看 ${expert.name} 详情`"
|
||||
:aria-expanded="openKey === expert.key"
|
||||
>
|
||||
|
|
@ -62,7 +62,7 @@
|
|||
>
|
||||
<span
|
||||
class="expert-list__avatar"
|
||||
:style="{ borderColor: expert.color }"
|
||||
:style="{ background: expert.color, color: 'var(--text-inverse)', borderColor: expert.color }"
|
||||
>{{ expert.avatar }}</span>
|
||||
<span class="expert-list__name">{{ expert.name }}</span>
|
||||
<span v-if="expert.isLead" class="expert-list__tag">Lead</span>
|
||||
|
|
@ -153,11 +153,40 @@ const allExperts = computed<IExpertDisplay[]>(() => {
|
|||
}
|
||||
if (mode.value === 'board') {
|
||||
const list = chatStore.boardState?.experts ?? []
|
||||
// 颜色一致性:优先使用当前会话 messages 中最近的 board_speech/
|
||||
// round_summary 的 expert_color(与 MessageShell 头像同源),保证
|
||||
// 顶部卡片头像与对话头像颜色严格匹配;缺失时回退到 boardState
|
||||
// 中保存的 YAML color。
|
||||
const liveColorByName = new Map<string, string>()
|
||||
const liveAvatarByName = new Map<string, string>()
|
||||
const conv = chatStore.conversations?.find(
|
||||
(c: { id: string; messages?: unknown[] }) => c.id === chatStore.currentConversationId,
|
||||
)
|
||||
if (conv?.messages) {
|
||||
// walk from latest to earliest to capture the most recent identity
|
||||
for (let i = conv.messages.length - 1; i >= 0; i--) {
|
||||
const m = conv.messages[i] as {
|
||||
message_type?: string
|
||||
expert_name?: string
|
||||
expert_color?: string
|
||||
expert_avatar?: string
|
||||
}
|
||||
if (
|
||||
(m.message_type === 'board_speech' ||
|
||||
m.message_type === 'board_summary') &&
|
||||
m.expert_name &&
|
||||
!liveColorByName.has(m.expert_name)
|
||||
) {
|
||||
if (m.expert_color) liveColorByName.set(m.expert_name, m.expert_color)
|
||||
if (m.expert_avatar) liveAvatarByName.set(m.expert_name, m.expert_avatar)
|
||||
}
|
||||
}
|
||||
}
|
||||
return list.map((e: IBoardExpert, idx: number) => ({
|
||||
key: `${e.name}-${idx}`,
|
||||
name: e.name,
|
||||
avatar: e.avatar,
|
||||
color: e.color,
|
||||
avatar: liveAvatarByName.get(e.name) ?? e.avatar,
|
||||
color: liveColorByName.get(e.name) ?? e.color,
|
||||
persona: e.persona,
|
||||
description: e.is_moderator ? '主持人' : undefined,
|
||||
isLead: false,
|
||||
|
|
|
|||
|
|
@ -14,20 +14,20 @@ export interface ExpertIdentity {
|
|||
color: string
|
||||
}
|
||||
|
||||
/** Non-blue palette. Order is stable — do not reshuffle (would break identity). */
|
||||
/** Neutral slate palette (GitHub/professional style). Order is stable — do not reshuffle. */
|
||||
const PALETTE: ReadonlyArray<string> = [
|
||||
"#d97706", // amber 600
|
||||
"#059669", // emerald 600
|
||||
"#7c3aed", // violet 600
|
||||
"#db2777", // pink 600
|
||||
"#0e7490", // cyan 700
|
||||
"#65a30d", // lime 600
|
||||
"#c2410c", // orange 700
|
||||
"#9333ea", // purple 600
|
||||
"#0891b2", // sky 600
|
||||
"#a16207", // yellow 700
|
||||
"#be185d", // rose 700
|
||||
"#15803d", // green 700
|
||||
"#4b5563", // slate 600
|
||||
"#6b7280", // gray 500
|
||||
"#9ca3af", // gray 400
|
||||
"#374151", // gray 800
|
||||
"#1f2937", // gray 900
|
||||
"#1e40af", // blue 800 (deep, non-vivid)
|
||||
"#166534", // green 800
|
||||
"#7c2d12", // orange 900
|
||||
"#581c87", // purple 900
|
||||
"#155e75", // cyan 800
|
||||
"#92400e", // amber 800
|
||||
"#78716c", // stone 500
|
||||
]
|
||||
|
||||
/** djb2-style string hash — stable across JS engines and reloads. */
|
||||
|
|
|
|||
|
|
@ -55,19 +55,19 @@ export function resolveMessageType(message: IChatMessage): MessageViewType {
|
|||
switch (message.message_type) {
|
||||
case 'plan_update':
|
||||
return 'team_plan'
|
||||
// 2026-07-01: board_* events render as plain assistant bubbles (streaming).
|
||||
// The user's first @board/@team message already shows a structured card
|
||||
// (UserBubble) with topic + expert count + expert list; rendering
|
||||
// dedicated cards (BoardBannerCard / BoardRoundCard / BoardConclusionCard)
|
||||
// for the subsequent board_started/board_speech/board_summary/
|
||||
// board_conclusion events duplicates the banner and breaks the natural
|
||||
// chat flow. Fall through to 'assistant' so the content streams
|
||||
// inline.
|
||||
// 2026-07-02: 恢复 board_* 事件走专用卡片路径,落地方案B
|
||||
// (中性灰阶头像/名字/颜色徽章/对话气泡 + 左侧3px色条)。
|
||||
// UserBubble 仅渲染用户输入的 @board 指令文本卡片,
|
||||
// BoardBannerCard 渲染后端回送的 board_started 事件 (含专家 chip + 进度条),
|
||||
// 二者职责不同,不构成重复。
|
||||
case 'board_started':
|
||||
return 'board_banner'
|
||||
case 'board_speech':
|
||||
return 'board_speech'
|
||||
case 'board_summary':
|
||||
return 'board_summary'
|
||||
case 'board_conclusion':
|
||||
return 'assistant'
|
||||
return 'board_conclusion'
|
||||
case 'debate_started':
|
||||
return 'debate_started'
|
||||
case 'debate_argument':
|
||||
|
|
@ -149,42 +149,34 @@ export function useMessageRenderer(message: IChatMessage) {
|
|||
}
|
||||
}
|
||||
|
||||
case 'board_speech':
|
||||
case 'board_speech': {
|
||||
const roleTag = message.board_role === 'moderator' ? '主持' : '专家'
|
||||
return {
|
||||
type,
|
||||
shell: {
|
||||
name: message.expert_name || '专家',
|
||||
meta: message.board_round ? `第 ${message.board_round} 轮${message.board_role === 'moderator' ? ' · 主持' : ''}` : time,
|
||||
meta: message.board_round ? `第 ${message.board_round} 轮 · ${roleTag}` : `${roleTag}`,
|
||||
avatar: message.expert_avatar,
|
||||
color: message.expert_color,
|
||||
},
|
||||
component: BoardRoundCard,
|
||||
props: {
|
||||
name: message.expert_name || '专家',
|
||||
avatar: message.expert_avatar || '',
|
||||
color: message.expert_color,
|
||||
round: message.board_round,
|
||||
role: message.board_role === 'moderator' ? 'moderator' : 'expert',
|
||||
content: message.content || '',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
case 'board_summary':
|
||||
return {
|
||||
type,
|
||||
shell: {
|
||||
name: message.expert_name || '主持人',
|
||||
meta: message.board_round ? `第 ${message.board_round} 轮 · 小结` : time,
|
||||
meta: message.board_round ? `第 ${message.board_round} 轮 · 小结` : '小结',
|
||||
avatar: message.expert_avatar,
|
||||
color: message.expert_color,
|
||||
},
|
||||
component: BoardRoundCard,
|
||||
props: {
|
||||
name: message.expert_name || '主持人',
|
||||
avatar: message.expert_avatar || '',
|
||||
color: message.expert_color,
|
||||
round: message.board_round,
|
||||
role: 'summary',
|
||||
content: message.content || '',
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,7 @@
|
|||
<template>
|
||||
<div class="board-round-card" :class="[`board-round-card--${role}`]">
|
||||
<div class="board-round-card__header">
|
||||
<span
|
||||
class="board-round-card__avatar"
|
||||
:style="avatarStyle"
|
||||
>
|
||||
{{ avatar || name.charAt(0) }}
|
||||
</span>
|
||||
<span class="board-round-card__name">{{ name }}</span>
|
||||
<span v-if="round" class="board-round-card__round">第 {{ round }} 轮</span>
|
||||
<span v-if="roleTag" class="board-round-card__role">{{ roleTag }}</span>
|
||||
</div>
|
||||
<div class="board-round-card__content">
|
||||
<div class="board-round-card">
|
||||
<AssistantText :message="textMessage" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
@ -23,37 +10,16 @@ import type { IChatMessage } from '@/api/types'
|
|||
import AssistantText from './AssistantText.vue'
|
||||
|
||||
interface Props {
|
||||
name: string
|
||||
avatar?: string
|
||||
color?: string
|
||||
round?: number
|
||||
role?: 'moderator' | 'expert' | 'summary'
|
||||
content: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
avatar: '',
|
||||
role: 'expert',
|
||||
})
|
||||
|
||||
const roleTag = computed(() => {
|
||||
const tags: Record<string, string> = {
|
||||
moderator: '主持',
|
||||
expert: '专家',
|
||||
summary: '小结',
|
||||
}
|
||||
return tags[props.role] || ''
|
||||
})
|
||||
|
||||
const avatarStyle = computed(() => {
|
||||
if (props.color) {
|
||||
return { background: props.color }
|
||||
}
|
||||
return { background: 'var(--accent-board)' }
|
||||
})
|
||||
const props = defineProps<Props>()
|
||||
|
||||
// 方案B: BoardRoundCard 仅作为 Board 发言的纯内容容器,
|
||||
// 渲染 AssistantText 的流式文本。头像/名字/轮次/角色标签等元信息
|
||||
// 全部由外层 MessageShell 的 header 负责,避免重复渲染。
|
||||
const textMessage = computed<IChatMessage>(() => ({
|
||||
id: `board-${props.name}-${props.round || 0}`,
|
||||
id: `board-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
||||
role: 'assistant',
|
||||
content: props.content,
|
||||
timestamp: new Date().toISOString(),
|
||||
|
|
@ -64,63 +30,5 @@ const textMessage = computed<IChatMessage>(() => ({
|
|||
<style scoped>
|
||||
.board-round-card {
|
||||
width: 100%;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-left: 3px solid var(--accent-board);
|
||||
border-radius: var(--radius-card);
|
||||
box-shadow: var(--shadow-card);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.board-round-card--summary {
|
||||
background: var(--accent-board-soft);
|
||||
border-left-color: var(--accent-board);
|
||||
}
|
||||
|
||||
.board-round-card__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.board-round-card__avatar {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: var(--radius-full);
|
||||
font-size: 12px;
|
||||
color: var(--text-inverse);
|
||||
}
|
||||
|
||||
.board-round-card__name {
|
||||
font-size: var(--font-sm);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.board-round-card__round {
|
||||
font-size: var(--font-xs);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.board-round-card__role {
|
||||
margin-left: auto;
|
||||
font-size: var(--font-xs);
|
||||
padding: 1px 6px;
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--accent-board-soft);
|
||||
color: var(--accent-board);
|
||||
}
|
||||
|
||||
.board-round-card__content {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
}
|
||||
|
||||
.board-round-card__content :deep(p) {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -20,13 +20,13 @@
|
|||
</div>
|
||||
<div class="message-shell__body">
|
||||
<div class="message-shell__header">
|
||||
<!-- U4 R10: 专家身份 badge — 用 expert_color 高亮 expert_name,流式期间及结束后均保留 -->
|
||||
<!-- 方案B: 专家名字始终是纯文本 (粗体),彩色身份由头像背景承担,
|
||||
不再渲染彩色 pill 名字徽章 -->
|
||||
<span
|
||||
v-if="expertName"
|
||||
class="message-shell__expert-badge"
|
||||
:style="{ backgroundColor: expertColor || '#1890ff' }"
|
||||
>{{ expertName }}</span>
|
||||
<span v-else class="message-shell__name">{{ name }}</span>
|
||||
v-if="expertName || name"
|
||||
class="message-shell__name"
|
||||
:class="{ 'message-shell__name--expert': !!expertName }"
|
||||
>{{ expertName || name }}</span>
|
||||
<span v-if="meta" class="message-shell__meta">{{ meta }}</span>
|
||||
<span
|
||||
v-if="streaming"
|
||||
|
|
@ -152,20 +152,10 @@ withDefaults(defineProps<Props>(), {
|
|||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
/* U4 R10: 专家身份 badge — 彩色 pill,区别于普通 name 文本与 avatar */
|
||||
.message-shell__expert-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0 var(--space-2);
|
||||
border-radius: var(--radius-full);
|
||||
color: var(--text-inverse, #fff);
|
||||
font-size: var(--font-xs);
|
||||
/* 方案B: 专家名字 — 粗体文本 + 略深色,与普通 name 区分 (无 pill 背景) */
|
||||
.message-shell__name--expert {
|
||||
color: var(--text-primary);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
line-height: 1.6;
|
||||
max-width: 12em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.message-shell__meta {
|
||||
|
|
|
|||
|
|
@ -14,37 +14,16 @@
|
|||
/>
|
||||
</MessageShell>
|
||||
|
||||
<MessageShell role="assistant" name="架构师老张" meta="第 1 轮" avatar="张" color="var(--accent-board)">
|
||||
<BoardRoundCard
|
||||
name="架构师老张"
|
||||
avatar="张"
|
||||
color="var(--accent-board)"
|
||||
:round="1"
|
||||
role="expert"
|
||||
:content="speech1.content"
|
||||
/>
|
||||
<MessageShell role="assistant" name="架构师老张" meta="第 1 轮 · 专家" avatar="张" color="var(--accent-board)">
|
||||
<BoardRoundCard :content="speech1.content" />
|
||||
</MessageShell>
|
||||
|
||||
<MessageShell role="assistant" name="后端负责人小李" meta="第 1 轮" avatar="李" color="var(--accent-board)">
|
||||
<BoardRoundCard
|
||||
name="后端负责人小李"
|
||||
avatar="李"
|
||||
color="var(--accent-board)"
|
||||
:round="1"
|
||||
role="expert"
|
||||
:content="speech2.content"
|
||||
/>
|
||||
<MessageShell role="assistant" name="后端负责人小李" meta="第 1 轮 · 专家" avatar="李" color="var(--accent-board)">
|
||||
<BoardRoundCard :content="speech2.content" />
|
||||
</MessageShell>
|
||||
|
||||
<MessageShell role="assistant" name="主持人" meta="第 1 轮 · 小结" avatar="主" color="var(--accent-board)">
|
||||
<BoardRoundCard
|
||||
name="主持人"
|
||||
avatar="主"
|
||||
color="var(--accent-board)"
|
||||
:round="1"
|
||||
role="summary"
|
||||
:content="summaryMessage.content"
|
||||
/>
|
||||
<BoardRoundCard :content="summaryMessage.content" />
|
||||
</MessageShell>
|
||||
|
||||
<MessageShell role="assistant" name="主持人" meta="11:05">
|
||||
|
|
|
|||
|
|
@ -1296,14 +1296,17 @@ export function dispatchWsEvent(
|
|||
(c) => c.id === conversationId,
|
||||
);
|
||||
if (!conv) break;
|
||||
// Stable identity for the moderator, just like expert_speech.
|
||||
// Stable identity for the moderator. Prefer the event's
|
||||
// moderator_avatar/moderator_color (added in 2026-07-02 so persistence
|
||||
// has the same identity) and fall back to the boardState snapshot
|
||||
// captured at board_started.
|
||||
const moderator = state.boardState.value?.experts.find(
|
||||
(e) => e.name === summaryData.moderator_name,
|
||||
);
|
||||
const identity = resolveExpertIdentity(
|
||||
summaryData.moderator_name,
|
||||
moderator?.avatar,
|
||||
moderator?.color,
|
||||
summaryData.moderator_avatar || moderator?.avatar,
|
||||
summaryData.moderator_color || moderator?.color,
|
||||
);
|
||||
const summaryMsg: IChatMessage = {
|
||||
id: generateId(),
|
||||
|
|
|
|||
|
|
@ -181,6 +181,20 @@ onMounted(async () => {
|
|||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 显式绑定到 token — AntD ConfigProvider token 在 tauri 冷启动时序
|
||||
不稳定,这里兜底强制使用项目主色(#1a1a1a 近黑),避免蓝色兜底。 */
|
||||
.login-submit.ant-btn-primary,
|
||||
.login-submit.ant-btn-primary:hover,
|
||||
.login-submit.ant-btn-primary:focus {
|
||||
background-color: var(--color-primary, #1a1a1a);
|
||||
border-color: var(--color-primary, #1a1a1a);
|
||||
color: var(--text-inverse, #ffffff);
|
||||
}
|
||||
.login-submit.ant-btn-primary:hover {
|
||||
background-color: var(--color-primary-hover, #2f2f2f);
|
||||
border-color: var(--color-primary-hover, #2f2f2f);
|
||||
}
|
||||
|
||||
.login-remember {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ describe('transient state reset matrix', () => {
|
|||
content: '私董会开始:测试主题',
|
||||
timestamp: '2026-07-01T10:00:00Z',
|
||||
status: 'completed' as const,
|
||||
message_type: 'board_started',
|
||||
message_type: 'board_started' as const,
|
||||
board_started: {
|
||||
team_id: 'team-1',
|
||||
topic: '测试主题',
|
||||
|
|
@ -156,8 +156,8 @@ describe('transient state reset matrix', () => {
|
|||
]
|
||||
store.currentConversationId = 'conv-a'
|
||||
|
||||
const boardState = { topic: 'board in A', experts: [], max_rounds: 1, current_round: 0, status: 'discussing' }
|
||||
store.boardState = boardState as never
|
||||
const boardState = { topic: 'board in A', experts: [], max_rounds: 1, current_round: 0, status: 'discussing' as const }
|
||||
store.boardState = boardState
|
||||
store.debateState = { topic: 'debate' } as never
|
||||
store.collaborationState = { contracts: [], notices: [], reviews: [], risks: [] } as never
|
||||
|
||||
|
|
|
|||
|
|
@ -337,6 +337,8 @@ async def _execute_board_meeting(
|
|||
{
|
||||
"message_type": "board_summary",
|
||||
"expert_name": event_data.get("moderator_name"),
|
||||
"expert_avatar": event_data.get("moderator_avatar"),
|
||||
"expert_color": event_data.get("moderator_color"),
|
||||
"board_round": event_data.get("round"),
|
||||
"board_role": "summary",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,295 @@
|
|||
"""ReAct L4 真实 LLM smoke test (U6).
|
||||
|
||||
手动验证 U4 的 L0 规则重排(`_build_tool_use_prompt` 中"何时必须使用工具"前置)
|
||||
在真实 LLM 调用下是否生效 — Agent 面对复杂需求时是否调用 `web_search` 而非
|
||||
直接回答。
|
||||
|
||||
不进 CI(依赖真实 LLM API key + 网络)。运行方式:
|
||||
|
||||
python3 tests/manual/test_react_l4_smoke.py
|
||||
|
||||
判定标准(plan U6):
|
||||
- Probe 1-4 期望触发 web_search(≥3/4 pass 算通过)
|
||||
- Probe 5 期望不触发工具调用(验证 escape hatch 规则 4 仍有效)
|
||||
- 通过 → Bug 2 状态升级为 "L4 verified"
|
||||
- 未通过 → 触发 L1(工具描述扩展)独立 plan
|
||||
|
||||
ponytail: 直接复用 cli.chat._build_gateway + server.app._create_provider,
|
||||
不重新实现 provider 注册逻辑。升级路径:抽到 shared 模块供 cli/server/manual
|
||||
共用。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
# 确保可以 import agentkit 包(脚本从仓库根目录运行)
|
||||
ROOT = Path(__file__).resolve().parent.parent.parent
|
||||
sys.path.insert(0, str(ROOT / "src"))
|
||||
|
||||
from agentkit.core.react import ReActEngine, ReActResult, ReActStep # noqa: E402
|
||||
from agentkit.server.config import find_config_path, load_config_with_dotenv # noqa: E402
|
||||
from agentkit.tools.web_search import WebSearchTool # noqa: E402
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Probe queries
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@dataclass
|
||||
class Probe:
|
||||
"""单个 probe query 及其期望行为。"""
|
||||
id: int
|
||||
category: str # external_info / realtime_data / multi_step / uncertain / no_tool
|
||||
query: str
|
||||
expect_tool_call: bool # True: 期望触发 web_search;False: 期望直接回答
|
||||
|
||||
|
||||
PROBES: list[Probe] = [
|
||||
Probe(
|
||||
id=1,
|
||||
category="external_info",
|
||||
query="收集 GitHub Trending 前 10 个项目信息并分析商业价值",
|
||||
expect_tool_call=True,
|
||||
),
|
||||
Probe(
|
||||
id=2,
|
||||
category="realtime_data",
|
||||
query="最新 AI 领域有什么重要新闻?",
|
||||
expect_tool_call=True,
|
||||
),
|
||||
Probe(
|
||||
id=3,
|
||||
category="multi_step",
|
||||
query="对比 React 和 Vue 3 在大型项目中的性能差异,给出具体数据",
|
||||
expect_tool_call=True,
|
||||
),
|
||||
Probe(
|
||||
id=4,
|
||||
category="realtime_data_simple",
|
||||
query="今天上海天气怎么样?",
|
||||
expect_tool_call=True,
|
||||
),
|
||||
Probe(
|
||||
id=5,
|
||||
category="no_tool_escape_hatch",
|
||||
query="请帮我总结下面这段文字:人工智能(AI)是计算机科学的一个分支,"
|
||||
"它企图了解智能的实质,并生产出一种新的能以人类智能相似的方式做出"
|
||||
"反应的智能机器。AI 的研究包括机器人、语言识别、图像识别、自然语言"
|
||||
"处理和专家系统等。",
|
||||
expect_tool_call=False,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Probe runner
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _count_tool_calls(result: ReActResult) -> tuple[int, list[str]]:
|
||||
"""统计 trajectory 中的 tool_call 步骤,返回 (count, tool_names)。"""
|
||||
tool_names: list[str] = []
|
||||
for step in result.trajectory:
|
||||
if step.action == "tool_call" and step.tool_name:
|
||||
tool_names.append(step.tool_name)
|
||||
return len(tool_names), tool_names
|
||||
|
||||
|
||||
def _format_trajectory(steps: list[ReActStep]) -> str:
|
||||
"""格式化 trajectory 用于报告输出。"""
|
||||
lines: list[str] = []
|
||||
for s in steps:
|
||||
if s.action == "tool_call":
|
||||
args_preview = str(s.arguments)[:80] if s.arguments else ""
|
||||
lines.append(f" [{s.step}] tool_call: {s.tool_name}({args_preview})")
|
||||
elif s.action == "final_answer":
|
||||
preview = (s.content or "")[:120].replace("\n", " ")
|
||||
lines.append(f" [{s.step}] final_answer: {preview}...")
|
||||
else:
|
||||
lines.append(f" [{s.step}] {s.action}")
|
||||
return "\n".join(lines) if lines else " (empty)"
|
||||
|
||||
|
||||
async def run_probe(engine: ReActEngine, probe: Probe) -> dict:
|
||||
"""运行单个 probe,返回结果字典。"""
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Probe #{probe.id} [{probe.category}]")
|
||||
print(f"Query: {probe.query[:80]}{'...' if len(probe.query) > 80 else ''}")
|
||||
print(f"Expect tool_call: {probe.expect_tool_call}")
|
||||
print(f"{'-'*60}")
|
||||
|
||||
messages = [{"role": "user", "content": probe.query}]
|
||||
start = time.monotonic()
|
||||
|
||||
try:
|
||||
result = await engine.execute(
|
||||
messages=messages,
|
||||
tools=[WebSearchTool()],
|
||||
model="default",
|
||||
agent_name="l4_smoke",
|
||||
task_type="smoke_test",
|
||||
)
|
||||
except Exception as e:
|
||||
elapsed = time.monotonic() - start
|
||||
print(f"ERROR after {elapsed:.1f}s: {type(e).__name__}: {e}")
|
||||
return {
|
||||
"probe_id": probe.id,
|
||||
"category": probe.category,
|
||||
"expect_tool_call": probe.expect_tool_call,
|
||||
"actual_tool_calls": 0,
|
||||
"tool_names": [],
|
||||
"status": "error",
|
||||
"error": f"{type(e).__name__}: {e}",
|
||||
"elapsed_s": elapsed,
|
||||
"output_preview": "",
|
||||
"trajectory": "",
|
||||
}
|
||||
|
||||
elapsed = time.monotonic() - start
|
||||
tool_count, tool_names = _count_tool_calls(result)
|
||||
|
||||
# 判定:实际触发工具 == 期望触发工具
|
||||
actual_triggered = tool_count > 0
|
||||
passed = actual_triggered == probe.expect_tool_call
|
||||
|
||||
# 特例:query 5 期望无工具,但如果 LLM 调用了,也算"行为可观察"(只是不符 escape hatch 期望)
|
||||
status = "pass" if passed else "fail"
|
||||
|
||||
output_preview = (result.output or "")[:200].replace("\n", " ")
|
||||
traj_str = _format_trajectory(result.trajectory)
|
||||
|
||||
print(f"Status: {status.upper()} ({elapsed:.1f}s, {result.total_steps} steps)")
|
||||
print(f"Tool calls: {tool_count} {tool_names}")
|
||||
print(f"Output preview: {output_preview}")
|
||||
print(f"Trajectory:\n{traj_str}")
|
||||
|
||||
return {
|
||||
"probe_id": probe.id,
|
||||
"category": probe.category,
|
||||
"expect_tool_call": probe.expect_tool_call,
|
||||
"actual_tool_calls": tool_count,
|
||||
"tool_names": tool_names,
|
||||
"status": status,
|
||||
"error": None,
|
||||
"elapsed_s": elapsed,
|
||||
"output_preview": output_preview,
|
||||
"trajectory": traj_str,
|
||||
"total_steps": result.total_steps,
|
||||
"total_tokens": result.total_tokens,
|
||||
"react_status": result.status,
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def main() -> int:
|
||||
"""运行所有 probe,输出报告,返回退出码(0=pass, 1=fail)。"""
|
||||
print("=" * 60)
|
||||
print("ReAct L4 Smoke Test (U6)")
|
||||
print("Verifies U4 L0 prompt rule rearrangement under real LLM calls")
|
||||
print("=" * 60)
|
||||
|
||||
# 1. 加载 server config(从 agentkit.yaml + env)
|
||||
print("\n[1/3] Loading server config...")
|
||||
try:
|
||||
config_path = find_config_path()
|
||||
if not config_path:
|
||||
print("FATAL: no agentkit.yaml found (./agentkit.yaml or ~/.agentkit/agentkit.yaml)")
|
||||
return 2
|
||||
server_config = load_config_with_dotenv(config_path)
|
||||
except Exception as e:
|
||||
print(f"FATAL: failed to load config: {type(e).__name__}: {e}")
|
||||
return 2
|
||||
|
||||
if not server_config.llm_config.providers:
|
||||
print("FATAL: no LLM providers configured (check agentkit.yaml / .env)")
|
||||
return 2
|
||||
|
||||
providers_with_key = [
|
||||
name for name, p in server_config.llm_config.providers.items()
|
||||
if p.api_key
|
||||
]
|
||||
if not providers_with_key:
|
||||
print("FATAL: no LLM providers with api_key set")
|
||||
print("Set env vars (e.g. OPENAI_API_KEY) or configure agentkit.yaml")
|
||||
return 2
|
||||
|
||||
print(f" Providers with key: {providers_with_key}")
|
||||
print(f" Default model alias: {server_config.llm_config.model_aliases.get('default', '<unset>')}")
|
||||
|
||||
# 2. 构建 LLM gateway + ReAct engine
|
||||
print("\n[2/3] Building LLM gateway + ReAct engine...")
|
||||
from agentkit.cli.chat import _build_gateway
|
||||
gateway = _build_gateway(server_config)
|
||||
if not gateway.has_providers:
|
||||
print("FATAL: gateway has no registered providers")
|
||||
return 2
|
||||
print(f" Gateway providers: {list(gateway._providers.keys())}")
|
||||
|
||||
engine = ReActEngine(
|
||||
llm_gateway=gateway,
|
||||
max_steps=8, # 留够步数让 LLM 多轮搜索
|
||||
default_timeout=120.0, # 单 query 上限 2 分钟
|
||||
enable_tool_search=False, # 简化:直接注入 web_search 完整描述
|
||||
)
|
||||
|
||||
# 3. 运行所有 probe
|
||||
print(f"\n[3/3] Running {len(PROBES)} probes...")
|
||||
results: list[dict] = []
|
||||
for probe in PROBES:
|
||||
# 每次重置 engine 状态(避免 loop detection 跨 query 误判)
|
||||
engine.reset()
|
||||
result = await run_probe(engine, probe)
|
||||
results.append(result)
|
||||
|
||||
# 4. 汇总报告
|
||||
print("\n" + "=" * 60)
|
||||
print("SUMMARY")
|
||||
print("=" * 60)
|
||||
print(f"{'#':>3} {'Category':<24} {'Expect':>8} {'Actual':>8} {'Status':>6} {'Time':>6}")
|
||||
print("-" * 60)
|
||||
for r in results:
|
||||
expect_str = "tool" if r["expect_tool_call"] else "direct"
|
||||
actual_str = f"{r['actual_tool_calls']} tool" if r["actual_tool_calls"] > 0 else "direct"
|
||||
print(
|
||||
f"{r['probe_id']:>3} "
|
||||
f"{r['category']:<24} "
|
||||
f"{expect_str:>8} "
|
||||
f"{actual_str:>8} "
|
||||
f"{r['status']:>6} "
|
||||
f"{r['elapsed_s']:>5.1f}s"
|
||||
)
|
||||
|
||||
# 5. 判定
|
||||
print("\n" + "-" * 60)
|
||||
tool_probes = [r for r in results if r["expect_tool_call"]]
|
||||
direct_probes = [r for r in results if not r["expect_tool_call"]]
|
||||
|
||||
tool_pass = sum(1 for r in tool_probes if r["status"] == "pass")
|
||||
direct_pass = sum(1 for r in direct_probes if r["status"] == "pass")
|
||||
|
||||
print(f"Tool-call probes: {tool_pass}/{len(tool_probes)} passed (threshold: ≥3/4)")
|
||||
print(f"Direct-answer probes: {direct_pass}/{len(direct_probes)} passed")
|
||||
|
||||
overall_pass = (tool_pass >= 3) and (direct_pass == len(direct_probes))
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
if overall_pass:
|
||||
print("VERDICT: PASS — Bug 2 status upgraded to 'L4 verified'")
|
||||
print("Action: update plan Progress table U6 → done")
|
||||
return 0
|
||||
else:
|
||||
print("VERDICT: FAIL — L0 rule rearrangement insufficient")
|
||||
print("Action: trigger L1 (web_search description expansion) as independent plan")
|
||||
print(" also consider L2 (PLAN_EXEC phase policy) if L1 alone insufficient")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(asyncio.run(main()))
|
||||
|
|
@ -788,7 +788,7 @@ class TestLoginCommand:
|
|||
with open(config_path) as f:
|
||||
cfg = yaml.safe_load(f)
|
||||
assert cfg["token"] == "jwt-token-123"
|
||||
assert cfg["server_url"] == "http://localhost:8001"
|
||||
assert cfg["server_url"] == "http://localhost:18001"
|
||||
|
||||
def test_login_with_server_url(self):
|
||||
"""admin login --server-url saves the custom URL."""
|
||||
|
|
|
|||
Loading…
Reference in New Issue