Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 91685ebfdd | |||
| 65910f5417 | |||
| 17b87290c8 | |||
| bd5735f970 | |||
| 05f9112fab | |||
| b926b35703 | |||
| 8df1d4a83c | |||
| aad5a6b317 | |||
| ad34750075 | |||
| cd7e24cd3c | |||
| 0521b7b6f0 |
+23
-3
@@ -62,12 +62,30 @@ jobs:
|
||||
(echo '=== RETRY WITH VERBOSE ===' && \
|
||||
PYTHONPATH=$(pwd) /tmp/ci-venv-test/bin/pytest tests/ -m "not e2e" -x -v 2>&1 | tail -30)
|
||||
|
||||
# ── Job 3: CI 失败通知 ───────────────────────────────
|
||||
# ── Job 3: Frontend Build ───────────────────────────
|
||||
frontend:
|
||||
runs-on: macos-arm64
|
||||
needs: lint
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install & Build
|
||||
run: |
|
||||
cd src/frontend
|
||||
npm ci || npm install
|
||||
npm run build
|
||||
|
||||
# ── Job 4: CI 失败通知 ───────────────────────────────
|
||||
# 使用 needs.<job>.result 直接判断,不查询 commit status API
|
||||
# 根因:notify 自身的 pending status 会污染 commit status 查询结果(竞态条件)
|
||||
notify-on-failure:
|
||||
runs-on: macos-arm64
|
||||
needs: [lint, test]
|
||||
needs: [lint, test, frontend]
|
||||
if: always()
|
||||
steps:
|
||||
- name: Check results and notify
|
||||
@@ -75,12 +93,13 @@ jobs:
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
LINT_RESULT: ${{ needs.lint.result }}
|
||||
TEST_RESULT: ${{ needs.test.result }}
|
||||
FRONTEND_RESULT: ${{ needs.frontend.result }}
|
||||
run: |
|
||||
echo "Lint result: $LINT_RESULT"
|
||||
echo "Test result: $TEST_RESULT"
|
||||
|
||||
# 只有 lint 或 test 明确失败时才发通知
|
||||
if [ "$LINT_RESULT" = "failure" ] || [ "$TEST_RESULT" = "failure" ]; then
|
||||
if [ "$LINT_RESULT" = "failure" ] || [ "$TEST_RESULT" = "failure" ] || [ "$FRONTEND_RESULT" = "failure" ]; then
|
||||
echo "CI has failures, sending notification..."
|
||||
|
||||
# 如果是 PR 事件,写评论通知
|
||||
@@ -90,6 +109,7 @@ jobs:
|
||||
FAILED_JOBS=""
|
||||
[ "$LINT_RESULT" = "failure" ] && FAILED_JOBS="${FAILED_JOBS}lint "
|
||||
[ "$TEST_RESULT" = "failure" ] && FAILED_JOBS="${FAILED_JOBS}test "
|
||||
[ "$FRONTEND_RESULT" = "failure" ] && FAILED_JOBS="${FAILED_JOBS}frontend "
|
||||
|
||||
curl -sf -X POST \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
|
||||
@@ -110,15 +110,7 @@ jobs:
|
||||
PR_AUTHOR=$(curl --max-time 5 -sf \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
"$API_URL/repos/$REPO/pulls?state=closed&sort=updated&order=desc&limit=10" | \
|
||||
python3 -c "
|
||||
import json, sys
|
||||
sha = '$COMMIT_SHA'
|
||||
for pr in json.load(sys.stdin):
|
||||
merge_sha = pr.get('merge_commit_sha', '') or ''
|
||||
if merge_sha.startswith(sha) or sha.startswith(merge_sha):
|
||||
print(pr['user']['login'])
|
||||
break
|
||||
" 2>/dev/null || echo "")
|
||||
python3 -c "import json,sys; sha='$COMMIT_SHA'; matches=[pr['user']['login'] for pr in json.load(sys.stdin) if (pr.get('merge_commit_sha','') or '').startswith(sha) or sha.startswith(pr.get('merge_commit_sha','') or '')]; print(matches[0] if matches else '')" 2>/dev/null || echo "")
|
||||
|
||||
# 确定通知对象
|
||||
if [ -n "$PR_AUTHOR" ]; then
|
||||
|
||||
@@ -208,7 +208,7 @@ class Blackboard:
|
||||
params.append(parent_task)
|
||||
if conditions:
|
||||
query += " WHERE " + " AND ".join(conditions)
|
||||
query += " ORDER BY priority ASC, created_at ASC"
|
||||
query += " ORDER BY priority ASC, created_at DESC"
|
||||
rows = conn.execute(query, params).fetchall()
|
||||
return [Task.from_row(r) for r in rows]
|
||||
finally:
|
||||
|
||||
@@ -51,17 +51,41 @@ class ToolchainContextSection:
|
||||
name: str = "toolchain_context"
|
||||
priority: int = 10
|
||||
|
||||
EVENT_LABELS_ZH: Dict[str, str] = {
|
||||
"review_request": "Review 请求",
|
||||
"review_result": "Review 结果",
|
||||
"review_merged": "PR 合并",
|
||||
"review_comment": "Review 评论",
|
||||
"review_updated": "Review 更新",
|
||||
"ci_failure": "CI 失败",
|
||||
"deploy_failure": "部署失败",
|
||||
"issue_assigned": "Issue 指派",
|
||||
"mention": "@提及",
|
||||
}
|
||||
|
||||
def render(self, context: PromptContext) -> str:
|
||||
event_type = context.event_type
|
||||
event_data: Dict = context.event_data or {}
|
||||
|
||||
# 事件类型中文标签
|
||||
event_label = self.EVENT_LABELS_ZH.get(event_type, event_type or '未知')
|
||||
|
||||
# from / to 信息
|
||||
to_agent = context.agent_id or ''
|
||||
from_agent = 'system'
|
||||
|
||||
# Part 1: 事件信息(现有模板引擎)
|
||||
if event_type in _TEMPLATE_MAP:
|
||||
variables = {k: str(v) for k, v in event_data.items()}
|
||||
event_text = render_template(event_type, variables)
|
||||
# 补充事件类型中文标签 + from/to
|
||||
header = f"- **事件类型**: {event_label}\n- **来源**: {from_agent}\n- **指派**: {to_agent}\n"
|
||||
event_text = header + "\n" + event_text
|
||||
else:
|
||||
lines = ["## 工具链事件", ""]
|
||||
lines.append(f"- **事件类型**: {event_type or '未知'}")
|
||||
lines.append(f"- **事件类型**: {event_label}")
|
||||
lines.append(f"- **来源**: {from_agent}")
|
||||
lines.append(f"- **指派**: {to_agent}")
|
||||
if event_data:
|
||||
lines.append("- **事件详情**:")
|
||||
for key, value in event_data.items():
|
||||
|
||||
@@ -17,7 +17,6 @@ import CourtCeremony from './components/CourtCeremony';
|
||||
import CourtDiscussion from './components/CourtDiscussion';
|
||||
import UsagePanel from './components/UsagePanel';
|
||||
import SettingsPanel from './components/SettingsPanel';
|
||||
import ToolchainPanel from './components/ToolchainPanel';
|
||||
import GlobalSearch from './components/GlobalSearch';
|
||||
import NotificationCenter from './components/NotificationCenter';
|
||||
|
||||
@@ -101,7 +100,6 @@ export default function App() {
|
||||
usage: <UsagePanel />,
|
||||
morning: <MorningPanel />,
|
||||
settings: <SettingsPanel />,
|
||||
toolchain: <ToolchainPanel />,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
import { api, AgentsStatusData } from '../api';
|
||||
import ToolchainPanel from './ToolchainPanel';
|
||||
|
||||
interface ServiceCheckResult {
|
||||
name: string;
|
||||
@@ -15,7 +16,7 @@ interface ServiceCheckResult {
|
||||
}
|
||||
|
||||
export default function SettingsPanel() {
|
||||
const [tab, setTab] = useState<'connections' | 'security' | 'version' | 'logs'>('connections');
|
||||
const [tab, setTab] = useState<'connections' | 'security' | 'version' | 'logs' | 'toolchain'>('connections');
|
||||
|
||||
// 接线状态巡检
|
||||
const [checking, setChecking] = useState(false);
|
||||
@@ -95,6 +96,7 @@ export default function SettingsPanel() {
|
||||
{ key: 'security' as const, label: '🛡️ 安全防务' },
|
||||
{ key: 'version' as const, label: '📦 版本更新' },
|
||||
{ key: 'logs' as const, label: '📋 城防日志' },
|
||||
{ key: 'toolchain' as const, label: '⛓️ 工具链' },
|
||||
].map((t) => (
|
||||
<button key={t.key} className={`btn ${tab === t.key ? 'btn-primary' : ''}`} onClick={() => setTab(t.key)}>
|
||||
{t.label}
|
||||
@@ -288,6 +290,9 @@ export default function SettingsPanel() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ========== 工具链 ========== */}
|
||||
{tab === 'toolchain' && <ToolchainPanel />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,28 @@
|
||||
*/
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const AGENT_NAMES: Record<string, string> = {
|
||||
'pangtong-fujunshi': '庞统',
|
||||
'simayi-challenger': '司马懿',
|
||||
'zhangfei-dev': '张飞',
|
||||
'guanyu-dev': '关羽',
|
||||
'zhaoyun-data': '赵云',
|
||||
'jiangwei-infra': '姜维',
|
||||
'system': '系统',
|
||||
};
|
||||
|
||||
const EVENT_LABELS: Record<string, string> = {
|
||||
'review_request': 'Review 请求',
|
||||
'review_result': 'Review 结果',
|
||||
'review_merged': 'PR 合并',
|
||||
'review_comment': 'Review 评论',
|
||||
'review_updated': 'Review 更新',
|
||||
'ci_failure': 'CI 失败',
|
||||
'deploy_failure': '部署失败',
|
||||
'issue_assigned': 'Issue 指派',
|
||||
'mention': '@提及',
|
||||
};
|
||||
|
||||
const STATUS_COLORS: Record<string, string> = {
|
||||
pending: '#f59e0b22', claimed: '#6a9eff22', working: '#6a9eff22',
|
||||
review: '#818cf822', done: '#2ecc8a22', failed: '#ef444422',
|
||||
@@ -36,6 +58,7 @@ export default function ToolchainPanel() {
|
||||
const [detail, setDetail] = useState<any>(null);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [filterMode, setFilterMode] = useState<'all' | 'pending'>('all');
|
||||
|
||||
const loadTasks = async (q?: string) => {
|
||||
setLoading(true);
|
||||
@@ -52,6 +75,10 @@ export default function ToolchainPanel() {
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const displayed = filterMode === 'pending'
|
||||
? tasks.filter(t => !['done', 'failed', 'cancelled'].includes(t.status))
|
||||
: tasks;
|
||||
|
||||
useEffect(() => { loadTasks(); }, []);
|
||||
|
||||
// 搜索防抖 300ms
|
||||
@@ -120,7 +147,19 @@ export default function ToolchainPanel() {
|
||||
padding: '3px 8px', borderRadius: 4, fontSize: 10,
|
||||
border: '1px solid #2a3550', background: '#161b2e', color: '#8899aa', cursor: 'pointer',
|
||||
}}>🔄</button>
|
||||
<span style={{ fontSize: 10, color: 'var(--muted)' }}>{tasks.length} 条</span>
|
||||
<button onClick={() => setFilterMode('all')} style={{
|
||||
padding: '3px 8px', borderRadius: 4, fontSize: 10,
|
||||
border: `1px solid ${filterMode === 'all' ? 'var(--acc)' : '#2a3550'}`,
|
||||
background: filterMode === 'all' ? 'var(--acc)22' : '#161b2e',
|
||||
color: filterMode === 'all' ? 'var(--acc)' : '#8899aa', cursor: 'pointer',
|
||||
}}>全部</button>
|
||||
<button onClick={() => setFilterMode('pending')} style={{
|
||||
padding: '3px 8px', borderRadius: 4, fontSize: 10,
|
||||
border: `1px solid ${filterMode === 'pending' ? 'var(--acc)' : '#2a3550'}`,
|
||||
background: filterMode === 'pending' ? 'var(--acc)22' : '#161b2e',
|
||||
color: filterMode === 'pending' ? 'var(--acc)' : '#8899aa', cursor: 'pointer',
|
||||
}}>未处理</button>
|
||||
<span style={{ fontSize: 10, color: 'var(--muted)' }}>{filterMode === 'pending' ? displayed.length : tasks.length} 条</span>
|
||||
</div>
|
||||
|
||||
{/* 事件列表 */}
|
||||
@@ -130,7 +169,7 @@ export default function ToolchainPanel() {
|
||||
{loading ? '加载中...' : '暂无工具链事件'}
|
||||
</div>
|
||||
)}
|
||||
{tasks.map((t: any) => (
|
||||
{displayed.map((t: any) => (
|
||||
<div key={t.id} onClick={() => setSelectedId(t.id)} style={{
|
||||
padding: '10px 14px', borderBottom: '1px solid var(--line)',
|
||||
cursor: 'pointer', transition: 'background .15s',
|
||||
@@ -151,6 +190,9 @@ export default function ToolchainPanel() {
|
||||
fontSize: 12, fontWeight: 500, color: '#dde4f8',
|
||||
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
|
||||
}}>{t.title}</div>
|
||||
<div style={{ fontSize: 10, color: 'var(--muted)', marginTop: 2 }}>
|
||||
{AGENT_NAMES['system'] || '系统'} → {AGENT_NAMES[t.assignee] || t.assignee || '?'}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -174,6 +216,9 @@ export default function ToolchainPanel() {
|
||||
<span style={{ fontSize: 10, color: 'var(--muted)' }}>{detail.id}</span>
|
||||
</div>
|
||||
<div style={{ fontSize: 18, fontWeight: 700, lineHeight: 1.3 }}>{detail.title}</div>
|
||||
<div style={{ fontSize: 11, color: 'var(--muted)', marginTop: 4 }}>
|
||||
{AGENT_NAMES['system'] || '系统'} → {AGENT_NAMES[detail.assignee] || detail.assignee || '?'}
|
||||
</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--muted)', marginTop: 6 }}>
|
||||
{fmtTime(detail.created_at)}
|
||||
</div>
|
||||
|
||||
@@ -120,7 +120,7 @@ export function isArchived(t: Task): boolean {
|
||||
export type TabKey =
|
||||
| 'tasks' | 'court' | 'monitor' | 'agents'
|
||||
| 'models' | 'skills' | 'sessions' | 'archives' | 'templates'
|
||||
| 'usage' | 'settings' | 'officials' | 'morning' | 'mail' | 'toolchain';
|
||||
| 'usage' | 'settings' | 'officials' | 'morning' | 'mail';
|
||||
|
||||
export const TAB_DEFS: { key: TabKey; label: string; icon: string }[] = [
|
||||
{ key: 'tasks', label: '任务看板', icon: '📜' },
|
||||
@@ -135,7 +135,6 @@ export const TAB_DEFS: { key: TabKey; label: string; icon: string }[] = [
|
||||
{ key: 'archives', label: '奏折阁', icon: '📜' },
|
||||
{ key: 'morning', label: '早朝简报', icon: '🌅' },
|
||||
{ key: 'templates', label: '任务模板', icon: '📋' },
|
||||
{ key: 'toolchain', label: '工具链', icon: '⛓️' },
|
||||
{ key: 'settings', label: '系统设置', icon: '⚙️' },
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user