Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ PR 创建时,Store 自动生成 `ReviewEntry[]`,包含所有其他 Agent 作

所有发往 engine 的 review 相关命令(`submit-for-review` / `submit-review` / `merge-pr`)都必须携带当前 GitHub Token;engine 的 `canMerge` / `mergePr` 根据 token 是否存在决定走真实 GitHub API 还是 mock 路径,缺失 token 会导致真实 PR 走到 mock merge。

`submit-review` 命令携带 `comment` 字段(来自自动审阅的 `review.comment`),确保 fix 闭环能获取具体审阅意见而非 fallback「审阅未通过」。

**自动合并的 mock 区分**:`handleReviewVerdict` 在 reviewer 全 approved 后,**仅当有 GitHub Token 时**才主动调 `mergePr` 自动合并;mock 模式(无 token)不自动合并,UI「合并」按钮仍可点,由用户决定时机手动合并——避免 mock merge 跟 GitHub 现实状态脱钩造成误判。

**审阅失败上限**:`ReviewEntry` 加 `status='failed'` 枚举 + `attempts` + `failureReason` 字段。`retryDeferredReviews` 给 `performReview` 传 `onFailed` 回调,累加 attempts;达 `MAX_REVIEW_ATTEMPTS=3` 后 status 置 `failed`,retry 自然停止。`handleReviewVerdict` 看到 `hasFailed=true` 时跳过 `fixBasedOnReviews`——「审阅多次跑不通」≠「内容拒绝」,不应让 agent 改代码方向,等用户手动处置(重试 / 改派 / 强制合并 / 打回)。
Expand Down
2 changes: 1 addition & 1 deletion docs/COMPONENT_PATTERNS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const emit = defineEmits<{...}>()
- `CreateTaskModal.vue` — 表单弹窗组件:收集 name / repoUrl / tokenBudget 创建 Agent,Vue `<Transition>` 原生过渡动画
- `SwarmConfirmModal.vue` — 确认弹窗组件:类型化图标 + 双按钮确认/取消,配合 useConfirm composable
- `SwarmToast.vue` — Toast 通知组件:类型化图标 + 进度条 + 自动消失,配合 useToast composable
- `AgentDetail.vue` — 聊天式多轮对话组件:Header + Info + PR 审阅 + 文件变更 + 聊天消息区(input/output/think 支持 **Markdown 渲染 + 代码语法高亮**,基于 `marked` + `highlight.js` + `dompurify` XSS 过滤;tool_call/mcp/tool_result 为可折叠气泡保持等宽纯文本,默认收起,点击展开查看完整内容)+ `<textarea>` 输入框(Enter 发送 / Shift+Enter 换行,自动增高)。Info 区任务指令从 `logs` 倒序查找最近 `input` 类型条目渲染,不再依赖 `agent.instruction`;用 `line-clamp-2` 限高最多 2 行(末尾自动 `…`),完整原文由 `:title` tooltip 展示,避免发长消息时把 Info 区撑大挤掉下方聊天面板。日期字段通过 new Date(string).toLocaleTimeString() 显示。滚动行为:通过 `scroll` 事件监听跟踪用户是否在底部 50px 范围内;当新消息到达或内容流式追加时,若用户之前处于底部则即时滚至底部,若用户已主动上滚则保持当前浏览位置。可折叠消息展开/收起后同样会跟进滚动。**滚动到底部按钮**:当用户向上滚动离开底部 50px 以上时,聊天区右下角显示浮动下箭头按钮(白色圆形 + 阴影 + 淡入淡出动画),点击后平滑滚动至最新消息;处于底部或空对话时自动隐藏。按钮放在外层 `flex-1 min-h-0 relative` wrapper 内、与 `overflow-y-auto` 滚动容器同级(**不能** 放在滚动容器子节点,否则 absolute 定位基准随内容一起滚出视口);切换 Agent 时 watch `agent.id` 中同步清零 `showScrollToBottom`,并附 `aria-label`/`title="滚动到底部"` 供屏读器与悬停提示。**输入框草稿缓存**:通过 `useSwarmStore` 的 `draftInputs` 在组件挂载时恢复、卸载时保存、切换 Agent 时自动迁移草稿内容,避免切视图丢失未发送的输入。所有消息气泡均带 `animate-message-enter` 进入动画(fade-in + slide-up)。PR 审阅区「打回」按钮带 tooltip 说明语义——清空审阅状态、切回就绪态等待用户发新指令,**不会自动让 Agent 修改**(避免历史文案「Agent 继续修改」的误导)。Reviewer 行额外支持 `failed` 状态:自动审阅多次失败(kimi 启动失败 / 卡死等)后用 amber AlertCircle 图标 + 「失败 (×N)」标签 + 截断展示 failureReason,鼠标悬停看完整原因;语义跟「已拒绝」不同——审阅没跑通而非内容被拒,需要人工处置
- `AgentDetail.vue` — 聊天式多轮对话组件:Header + Info + PR 审阅 + 文件变更 + 聊天消息区(input/output/think 支持 **Markdown 渲染 + 代码语法高亮**,基于 `marked` + `highlight.js` + `dompurify` XSS 过滤;tool_call/mcp/tool_result 为可折叠气泡保持等宽纯文本,默认收起,点击展开查看完整内容)+ `<textarea>` 输入框(Enter 发送 / Shift+Enter 换行,自动增高)。Info 区任务指令从 `logs` 倒序查找最近 `input` 类型条目渲染,不再依赖 `agent.instruction`;用 `line-clamp-2` 限高最多 2 行(末尾自动 `…`),完整原文由 `:title` tooltip 展示,避免发长消息时把 Info 区撑大挤掉下方聊天面板。日期字段通过 new Date(string).toLocaleTimeString() 显示。滚动行为:通过 `scroll` 事件监听跟踪用户是否在底部 50px 范围内;当新消息到达或内容流式追加时,若用户之前处于底部则即时滚至底部,若用户已主动上滚则保持当前浏览位置。可折叠消息展开/收起后同样会跟进滚动。**滚动到底部按钮**:当用户向上滚动离开底部 50px 以上时,聊天区右下角显示浮动下箭头按钮(白色圆形 + 阴影 + 淡入淡出动画),点击后平滑滚动至最新消息;处于底部或空对话时自动隐藏。按钮放在外层 `flex-1 min-h-0 relative` wrapper 内、与 `overflow-y-auto` 滚动容器同级(**不能** 放在滚动容器子节点,否则 absolute 定位基准随内容一起滚出视口);切换 Agent 时 watch `agent.id` 中同步清零 `showScrollToBottom`,并附 `aria-label`/`title="滚动到底部"` 供屏读器与悬停提示。**输入框草稿缓存**:通过 `useSwarmStore` 的 `draftInputs` 在组件挂载时恢复、卸载时保存、切换 Agent 时自动迁移草稿内容,避免切视图丢失未发送的输入。所有消息气泡均带 `animate-message-enter` 进入动画(fade-in + slide-up)。PR 审阅区「打回」按钮带 tooltip 说明语义——清空审阅状态、切回就绪态等待用户发新指令,**不会自动让 Agent 修改**(避免历史文案「Agent 继续修改」的误导)。Reviewer 行额外支持 `failed` 状态:自动审阅多次失败(kimi 启动失败 / 卡死等)后用 amber AlertCircle 图标 + 「失败 (×N)」标签 + 截断展示 failureReason,鼠标悬停看完整原因;语义跟「已拒绝」不同——审阅没跑通而非内容被拒,需要人工处置。`submitReview` emit 携带 `review.comment`,确保人工审阅时不丢失自动审阅产生的 comment
- `TaskCard.vue` — 卡片组件:Agent 状态 + Token 进度 + 审阅徽章;状态枚举含 `orphan`(已失效,表示本地缓存中存在但引擎 restore 列表中无对应 ID 的 agent);启动/停止/重启/删除按钮均按 `engineReady` prop 禁用,引擎 restore 完成前置灰;删除操作通过 `useConfirm()` 调用自定义确认弹窗
- `AnalyticsPanel.vue` — 数据展示型组件:状态分布、Token 排行、任务列表;列表项通过 `getLastInput(task.logs)` 展示最后一条 input 指令摘要,与 `AgentDetail.vue` 的 Info 区指令展示逻辑一致
- `SettingsPanel.vue` — 纯信息展示型设置面板,使用 lucide 图标 + code 标签展示命令指引
Expand Down
2 changes: 1 addition & 1 deletion docs/FRONTEND.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Vue 3 + TypeScript + Vite + Tailwind CSS + lucide-vue-next + Tauri v2 + @tauri-a
- `components/SwarmConfirmModal.vue` — 确认弹窗组件:支持 danger/warning/info 类型,配合 useConfirm 使用
- `components/SwarmToast.vue` — Toast 通知组件:支持 error/success/info/warning,自动消失
- `App.vue` — 主入口:布局框架 + 视图路由(dashboard/agent-detail/analytics)+ 全局事件处理(如文件 diff 查看)
- `components/AgentDetail.vue` — Agent 详情:`<textarea>` 指令输入(Enter 发送 / Shift+Enter 换行,自动增高)+ 日志流(含 think / tool_call / mcp / tool_result 可折叠气泡,默认收起,点击展开查看完整内容,智能滚动:通过 `scroll` 事件跟踪用户位置,若用户之前在底部 50px 内则即时跟进,已上滚则保持阅读位置)+ PR 审阅 + 文件变更(点击通过 engine 获取 diff)。**聊天消息气泡(input / output / think)支持 Markdown 渲染 + 代码语法高亮**,基于 `marked` + `highlight.js` + `dompurify`(XSS 过滤)。**滚动到底部按钮**:用户上滚离开底部后,聊天区右下角显示浮动下箭头按钮(白色圆形 + 阴影 + 淡入淡出动画),点击平滑滚动至底部;空对话或已处于底部时自动隐藏。按钮放在滚动容器的 **relative wrapper 兄弟节点** 而非 overflow-y-auto 子节点内(避免随消息滚出视口),切换 Agent 时清零 `showScrollToBottom`,带 `aria-label`/`title="滚动到底部"`。输入框草稿通过 `useSwarmStore.draftInputs` 做内存级缓存,切视图/切 Agent 时不丢失未发送内容。所有消息气泡带 `animate-message-enter` 进入动画。「打回」按钮 tooltip 说明:只切回就绪态、不自动触发修改。Reviewer 行支持 `failed` 状态展示(amber 配色 + 失败原因)。Info 区「任务指令」用 `line-clamp-2` 限高,长消息不会撑大挤掉聊天面板
- `components/AgentDetail.vue` — Agent 详情:`<textarea>` 指令输入(Enter 发送 / Shift+Enter 换行,自动增高)+ 日志流(含 think / tool_call / mcp / tool_result 可折叠气泡,默认收起,点击展开查看完整内容,智能滚动:通过 `scroll` 事件跟踪用户位置,若用户之前在底部 50px 内则即时跟进,已上滚则保持阅读位置)+ PR 审阅 + 文件变更(点击通过 engine 获取 diff)。**聊天消息气泡(input / output / think)支持 Markdown 渲染 + 代码语法高亮**,基于 `marked` + `highlight.js` + `dompurify`(XSS 过滤)。**滚动到底部按钮**:用户上滚离开底部后,聊天区右下角显示浮动下箭头按钮(白色圆形 + 阴影 + 淡入淡出动画),点击平滑滚动至底部;空对话或已处于底部时自动隐藏。按钮放在滚动容器的 **relative wrapper 兄弟节点** 而非 overflow-y-auto 子节点内(避免随消息滚出视口),切换 Agent 时清零 `showScrollToBottom`,带 `aria-label`/`title="滚动到底部"`。输入框草稿通过 `useSwarmStore.draftInputs` 做内存级缓存,切视图/切 Agent 时不丢失未发送内容。所有消息气泡带 `animate-message-enter` 进入动画。「打回」按钮 tooltip 说明:只切回就绪态、不自动触发修改。Reviewer 行支持 `failed` 状态展示(amber 配色 + 失败原因)。Info 区「任务指令」用 `line-clamp-2` 限高,长消息不会撑大挤掉聊天面板。审阅按钮 emit `submitReview` 时携带 `review.comment`,避免人工审批覆盖自动审阅产生的具体意见
- `composables/useConfirm.ts` — 全局确认弹窗状态管理(命令式 API)
- `composables/useToast.ts` — 全局 Toast 通知状态管理(命令式 API)
- `components/SettingsPanel.vue` — 系统设置(GitHub Token + Kimi CLI 安装指引)
Expand Down
62 changes: 0 additions & 62 deletions docs/exec-plans/backlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,52 +25,6 @@

## 🐛 活跃 Bug

### #1 — `submitReview` 丢失 reviewer 的具体评论

**现象**
PR #25 流程里 reviewer agent 给出了详细修改建议(如「按钮会随聊天内容滚走,建议放到 wrapper 外」),但 engine-state.json 里 `reviews[].comment` 字段全部缺失,前端 agent 收到的 fix 指令只剩 fallback「审阅未通过」空字符串,kimi 无据可改。

**根因(源码确认)**
- [agent.ts:1074-1117 submitReview](kimi-code-swarm/agent-engine/src/agent.ts#L1074-L1117):收到 `comment` 参数后**只用来拼 GitHub API 的 reviewBody**(1081 / 1091 / 1105 行),写入 review 记录时(1113-1114)只 set `status` 和 `reviewedAt`,**遗漏 `review.comment = comment`**。
- [agent.ts:1473 fixBasedOnReviews](kimi-code-swarm/agent-engine/src/agent.ts#L1473):读 `r.comment || '审阅未通过'` → 永远 `undefined` → fallback「审阅未通过」→ kimi 拿到 0 信息无据可改。

**修复方向**
1114 行后追加一行:`review.comment = comment`。

**优先级**:高。直接让 review→fix 闭环失效。

---

### #2 — `stop()` 不级联取消 async 链

**现象**
fixBasedOnReviews 进入循环后按「停止」按钮无反应;点多次也停不下,kimi 进程被 kill 但下一轮 `sendInstruction → autoSubmitForReview` 仍按计划起来。

**根因**
`stop()` 只 kill 当前 kimi 子进程,不取消 engine 里的异步链(fixBasedOnReviews → 等 reviewer → 再 sendInstruction → autoSubmitForReview)。没有 cancellation token / AbortSignal 贯穿。

**修复方向**
agent 上挂 `AbortController`,stop 时 abort;所有 await 链入口检查 `signal.aborted` 直接 throw / return。或更彻底——下放到 agent skill 后,停止只需 kill 进程即可,不再有 engine 编排的异步链。

**优先级**:高。用户能感知到「按钮坏了」。

---

### #3 — `syncBranchWithMain` 同步成功却报「同步提交失败」

**现象**
PR squash merge 后下一次 sendInstruction 触发 sync,UI 红字「同步提交失败」+ pre-commit 输出,但 pre-commit 自己写「✅ pre-commit 检查通过」,互相打架。

**根因**
[agent.ts:936-946](kimi-code-swarm/agent-engine/src/agent.ts#L936-L946):`gitMerge` 在 git 默认行为下成功时已自动建 merge commit,紧接着代码又调 `gitCommit('sync: merge origin/main')` —— 此时无暂存内容 → `nothing to commit` → exit 1 → 误报。squash merge 场景必触发(agent 分支 A+B+C ≡ main 的 squash commit S,merge 必为 clean)。

**修复方向**
删掉 merge 成功后的 `gitCommit` 调用,直接 `this.log('system', '已同步 ...') + return true`。

**优先级**:中。同步实际成功,纯 UI 噪音,但加重 "engine 修不完" 感受。归并到 [架构决策项] 整体下放更彻底。

---

### #4 — F5 刷新 dashboard 后 agents 不可见

**现象**
Expand All @@ -86,20 +40,4 @@ engine 新增 `list-agents` 命令:收到后全量 emit `agent-created` + `eng

---

### #5 — `getCheckRunLogs` 截断方向错(PR #28 重现)

**现象**
CI fix 轮里 agent 思维链都说「日志被截断了,找不到完整错误」,反翻 `.github/workflows/` 反推 CI 在跑什么。PR #28 那次:fix prompt 里 8KB 窗口前面全是 git config / fetch refs / setup auth 噪音,真错误 `ERR_MODULE_NOT_FOUND: @typescript-eslint/typescript-estree` 完全被截走,kimi 是靠自己另起 tool call 去 GitHub 拉完整 log 才查出根因。

**根因**
[github-api.ts:244 / :265](kimi-code-swarm/agent-engine/src/github-api.ts#L244):`text.slice(0, 8000)` 取前 8k 字符 —— 但 GitHub Actions log 开头全是 setup 噪音(runner 启动 / checkout / `npm install` deprecation warnings),**真正错误在末尾**。引入新依赖后 setup+install 轻松 8k+,错误被全截走。

**修复方向**
- 简版:`text.slice(-8000)` —— 错误几乎永远在末尾
- 稳版:首部 500(job 元信息)+ 末尾 7500 + 中间 `[middle truncated]` 标记

**优先级**:高。今晚 PR #28 再次证明不是偶发,会随依赖/复杂度增加越来越频繁。

---

*Updated: 2026-05-28 — 新增架构决策项 + 4 个活跃 bug(review.comment 丢失 / stop 不级联 / sync 误报 / F5 失踪)+ 重新收回 #5(CI log 截断方向,PR #28 重现)。*
13 changes: 6 additions & 7 deletions kimi-code-swarm/agent-engine/src/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,8 @@ export class Agent {
// 走流式 sendInstruction:修复过程实时显示在对话框,且无 runInstructionSilent 的硬超时;
// displayAsUserInput=false 让长 fix prompt 不污染「任务指令」区与聊天面板
await this.sendInstruction(fixPrompt, undefined, { displayAsUserInput: false })
// 若用户已点停止,中断重试循环
if (this.state.status === 'stopped') return
// 修复后重新检测变更
if (this.state.workspace) {
try {
Expand Down Expand Up @@ -935,13 +937,7 @@ export class Agent {

const mergeResult = await gitMerge(this.state.workspace, 'origin/main')
if (mergeResult.exitCode === 0) {
const commitResult = await gitCommit(this.state.workspace, 'sync: merge origin/main')
if (commitResult.exitCode === 0) {
this.log('system', `已同步 main 的 ${behindCount} 个 commit`)
} else {
this.log('error', `同步提交失败: ${commitResult.stderr}`)
try { await abortMerge(this.state.workspace) } catch { /* 已无 merge 状态可忽略 */ }
}
this.log('system', `已同步 main 的 ${behindCount} 个 commit`)
return true
}

Expand Down Expand Up @@ -1112,6 +1108,7 @@ ${fileContents.join('\n\n')}

review.status = approved ? 'approved' : 'rejected'
review.reviewedAt = new Date().toISOString()
if (comment !== undefined) review.comment = comment
this.syncState()
const action = approved ? '通过' : '拒绝'
this.log('system', `Agent「${review.reviewerName}」审阅${action}了此 PR`)
Expand Down Expand Up @@ -1481,6 +1478,8 @@ PR_BODY:
try {
// displayAsUserInput=false:审阅拒绝后引擎注入的修复 prompt 不算用户指令
await this.sendInstruction(prompt, undefined, { displayAsUserInput: false })
// 若用户已点停止,不再继续提交
if (this.state.status === 'stopped') return
// sendInstruction 完成后状态为 ready,改回 working 以符合 submitForReview 前置条件
this.state.status = 'working'
const { ok, steps } = await this.submitForReview(githubToken)
Expand Down
4 changes: 2 additions & 2 deletions kimi-code-swarm/agent-engine/src/github-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ export async function getCheckRunLogs(
const res = await fetch(url, { headers: getHeaders(token) })
if (res.ok) {
const text = await res.text()
return text.length > 8000 ? text.slice(0, 8000) + '\n...[truncated]' : text
return text.length > 8000 ? text.slice(-8000) + '\n...[truncated]' : text
}
console.error(`[github-api] job logs ${res.status}`)
} catch (err) {
Expand All @@ -262,7 +262,7 @@ export async function getCheckRunLogs(
return null
}
const text = await res.text()
return text.length > 8000 ? text.slice(0, 8000) + '\n...[truncated]' : text
return text.length > 8000 ? text.slice(-8000) + '\n...[truncated]' : text
} catch (err) {
console.error(`[github-api] getCheckRunLogs 异常: ${String(err)}`)
return null
Expand Down
6 changes: 3 additions & 3 deletions kimi-code-swarm/src/components/AgentDetail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
(e: 'submitForReview', id: string): void
(e: 'mergePr', id: string): void
(e: 'rejectPr', id: string): void
(e: 'submitReview', agentId: string, reviewerTaskId: string, approved: boolean): void
(e: 'submitReview', agentId: string, reviewerTaskId: string, approved: boolean, comment?: string): void
(e: 'showFileDiff', agentId: string, filePath: string): void
}>()

Expand Down Expand Up @@ -333,13 +333,13 @@
<div v-if="review.status === 'pending'" class="flex items-center gap-1">
<button
class="px-2 py-0.5 rounded bg-emerald-50 text-emerald-600 hover:bg-emerald-100 transition-colors"
@click="emit('submitReview', agent.id, review.reviewerTaskId, true)"
@click="emit('submitReview', agent.id, review.reviewerTaskId, true, review.comment)"
>
通过
</button>
<button
class="px-2 py-0.5 rounded bg-red-50 text-red-600 hover:bg-red-100 transition-colors"
@click="emit('submitReview', agent.id, review.reviewerTaskId, false)"
@click="emit('submitReview', agent.id, review.reviewerTaskId, false, review.comment)"
>
拒绝
</button>
Expand Down Expand Up @@ -401,7 +401,7 @@
<div v-if="log.type === 'input'" class="flex justify-end animate-message-enter">
<div class="flex items-end gap-2 max-w-[85%] min-w-0">
<div class="bg-swarm-600 text-white rounded-2xl rounded-tr-sm px-4 py-2.5 shadow-sm">
<div class="markdown-content markdown-content-inverted" v-html="renderMarkdown(log.content)" />

Check warning on line 404 in kimi-code-swarm/src/components/AgentDetail.vue

View workflow job for this annotation

GitHub Actions / check

'v-html' directive can lead to XSS attack
<div class="flex items-center justify-end gap-2 mt-1">
<span v-if="log.tokens" class="text-[10px] opacity-60">{{ log.tokens }} tokens</span>
<span class="text-[10px] opacity-60">{{ new Date(log.timestamp).toLocaleTimeString() }}</span>
Expand All @@ -420,7 +420,7 @@
<Bot class="w-3.5 h-3.5 text-blue-600" />
</div>
<div class="bg-white text-gray-800 rounded-2xl rounded-tl-sm px-4 py-2.5 shadow-sm border border-gray-100">
<div class="markdown-content" v-html="renderMarkdown(log.content)" />

Check warning on line 423 in kimi-code-swarm/src/components/AgentDetail.vue

View workflow job for this annotation

GitHub Actions / check

'v-html' directive can lead to XSS attack
<div class="flex items-center gap-2 mt-1">
<span class="text-[10px] text-gray-400">{{ new Date(log.timestamp).toLocaleTimeString() }}</span>
</div>
Expand All @@ -444,7 +444,7 @@
<ChevronDown v-else class="w-3.5 h-3.5 text-amber-600 shrink-0 ml-2" />
</button>
<div v-if="isLogExpanded(log.id)" class="px-4 pb-2.5">
<div class="markdown-content" v-html="renderMarkdown(log.content)" />

Check warning on line 447 in kimi-code-swarm/src/components/AgentDetail.vue

View workflow job for this annotation

GitHub Actions / check

'v-html' directive can lead to XSS attack
<div class="flex items-center gap-2 mt-1">
<span class="text-[10px] text-amber-400">{{ new Date(log.timestamp).toLocaleTimeString() }}</span>
</div>
Expand Down
Loading
Loading