Skip to content

Commit a82684f

Browse files
committed
feat: chat pipeline tracing + markdown table scroll fix (v0.1.13)
- 新增 [trace:cap] 全链路追踪日志(home/agent/runner/llm/client) 覆盖 chat.start → agent.iter → tool → message_stop 全生命周期 max_tokens 场景提为 warn 级,定位「干着干着断了」问题 - 修复 Markdown 宽表被 bubble 裁切:table 外包滚动层 + CSS class 移除 inline style,改用 .markdown-table-wrap 样式 - runner.ts 增加 stopReason 追踪(converged/max_supersteps/aborted)
1 parent 0c04aca commit a82684f

9 files changed

Lines changed: 200 additions & 39 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "one",
3-
"version": "0.1.12",
3+
"version": "0.1.13",
44
"description": "One desktop app built with Electron, React, and TypeScript.",
55
"main": "./out/main/index.cjs",
66
"author": "shijianzhong",

src/main/ipc/home.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,15 @@ export function registerHomeHandlers(): void {
452452
`caps=[${mentions.capabilities.map((c) => c.name).join(',')}] skills=[${mentions.skills.map((s) => s.name).join(',')}] ` +
453453
`→ ${focusCap ? 'focusCap(主Agent介绍/组队)' : directAgent ? 'directAgent' : '主Agent路由'}`,
454454
)
455+
logger.info(
456+
`[trace:cap] home.chat.start session=${sid} focusCap=${focusCap?.id ?? '-'}(${focusCap?.name ?? '-'}) ` +
457+
`msgLen=${message.length} msgHead=${JSON.stringify(message.slice(0, 80))}`,
458+
)
459+
signal.addEventListener(
460+
'abort',
461+
() => logger.warn(`[trace:cap] home.signal.abort session=${sid}`),
462+
{ once: true },
463+
)
455464

456465
try {
457466
if (directAgent) {
@@ -464,9 +473,11 @@ export function registerHomeHandlers(): void {
464473
)
465474
if (!graph) throw new Error('组队图构建失败')
466475
const question = mentions.cleanText || message
476+
logger.info(`[trace:cap] home.directAgent → runTeam agents=${directAgent.map((a) => a.id).join(',')}`)
467477
const result = await runTeam(graph, question, sid, buildDeps, emitStream, signal)
468478
addMessage({ sessionId: sid, role: 'assistant', content: result.output })
469479
emitStream({ type: 'message_stop', stop_reason: 'end_turn' })
480+
logger.info(`[trace:cap] home.directAgent.end session=${sid} outputLen=${result.output.length}`)
470481
return { runId: sid }
471482
}
472483

@@ -509,6 +520,10 @@ export function registerHomeHandlers(): void {
509520
if (result.hitIterationLimit) {
510521
logger.warn('[home] 主 Agent 达工具轮次上限,已强制无工具收尾')
511522
}
523+
logger.info(
524+
`[trace:cap] home.mainAgent.end session=${sid} hitIterLimit=${result.hitIterationLimit} ` +
525+
`finalTextLen=${finalText.length} textTail=${JSON.stringify(finalText.slice(-80))}`,
526+
)
512527

513528
// —— 创建幻觉补跑:自称已入库 / 否认持久化,但从未调 propose_* → 按 kind 定向挂工具再跑 ——
514529
// 挡「嘴上创建成功、确认卡从未出现」;澄清追问不触发(见 needsCreateRecovery)。
@@ -591,13 +606,25 @@ export function registerHomeHandlers(): void {
591606

592607
// 流结束:判定直答 vs 组队
593608
const decision = detector.decide()
609+
logger.info(
610+
`[trace:cap] home.router.decide session=${sid} kind=${decision.kind}` +
611+
(decision.kind === 'team' ? ` json=${JSON.stringify(decision.json)}` : ''),
612+
)
594613
if (decision.kind === 'team') {
595614
// 组队:拼编排图跑 runner,事件经 orch_event 转前端
596615
logger.info('[home-router] 判为组队:', JSON.stringify(decision.json))
597616
emitStream({ type: 'run_id', sessionId: sid })
598617
const graph = buildTeamGraph(decision.json, getAgent, getCapability)
599618
if (graph) {
619+
logger.info(
620+
`[trace:cap] home.team.run session=${sid} graphNodes=${graph.nodes.length} ` +
621+
`types=[${graph.nodes.map((n) => `${n.id}:${n.type}`).join(',')}]`,
622+
)
600623
const teamResult = await runTeam(graph, message, sid, buildDeps, emitStream, signal)
624+
logger.info(
625+
`[trace:cap] home.team.done session=${sid} outputLen=${teamResult.output.length} ` +
626+
`tail=${JSON.stringify(teamResult.output.slice(-80))}`,
627+
)
601628
addMessage({
602629
sessionId: sid,
603630
role: 'assistant',
@@ -611,6 +638,7 @@ export function registerHomeHandlers(): void {
611638
} else {
612639
// 组队 JSON 指向的 role/capability 全失效 → 回退直答
613640
logger.warn('[home-router] 组队图构建失败(role/capability 失效),回退直答')
641+
logger.warn(`[trace:cap] home.team.graph_build_failed session=${sid} json=${JSON.stringify(decision.json)}`)
614642
const direct = detector.flushDirect()
615643
if (direct) emitStream({ type: 'text', text: direct })
616644
addMessage({
@@ -625,6 +653,9 @@ export function registerHomeHandlers(): void {
625653
}
626654
} else {
627655
// 直答:flush 尾窗残留推前端,存档
656+
logger.info(
657+
`[trace:cap] home.direct session=${sid} focusCap=${!!focusCap} finalTextLen=${finalText.length}`,
658+
)
628659
const tail = detector.flushDirect()
629660
if (tail) emitStream({ type: 'text', text: tail })
630661
addMessage({
@@ -644,13 +675,16 @@ export function registerHomeHandlers(): void {
644675
)
645676

646677
// 10. 结束事件:触顶收尾用 max_iterations,便于前端/日志区分假 end_turn
678+
const stopReason = result.hitIterationLimit ? 'max_iterations' : 'end_turn'
679+
logger.info(`[trace:cap] home.message_stop session=${sid} reason=${stopReason} aborted=${signal.aborted}`)
647680
emitStream({
648681
type: 'message_stop',
649-
stop_reason: result.hitIterationLimit ? 'max_iterations' : 'end_turn',
682+
stop_reason: stopReason,
650683
})
651684
} catch (e) {
652685
// 错误推到 AI 气泡位置(而非聊天区上方),含可重试提示
653686
const msg = e instanceof Error ? e.message : String(e)
687+
logger.error(`[trace:cap] home.error session=${sid} aborted=${signal.aborted} err=${msg}`, e)
654688
emitStream({ type: 'error', error: msg })
655689
emitStream({ type: 'message_stop', stop_reason: 'error' })
656690
throw e

src/main/llm/client.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,22 @@ export class LLMClient {
8181
}
8282

8383
const finalMessage = await stream.finalMessage()
84-
logger.debug('[llm] finalMessage content types', finalMessage.content.map((b: { type: string }) => b.type))
85-
// 清理最终 text 中的 thinking 标签(中转代理可能把标签留在 text block 里)
8684
const cleanedContent = fromAnthropicContent(finalMessage.content).map(stripThinkingTags)
85+
const textLen = cleanedContent
86+
.filter((b): b is { type: 'text'; text: string } => b.type === 'text')
87+
.reduce((n, b) => n + b.text.length, 0)
88+
const toolNames = cleanedContent
89+
.filter((b): b is { type: 'tool_use'; id: string; name: string; input: unknown } => b.type === 'tool_use')
90+
.map((b) => b.name)
91+
// info:便于追踪「干着干着断了」——尤其 stop=max_tokens
92+
logger.info(
93+
`[trace:cap] llm.final model=${req.model} stop=${finalMessage.stop_reason ?? 'null'} ` +
94+
`textLen=${textLen} tools=[${toolNames.join(',')}] ` +
95+
`usage=${JSON.stringify(finalMessage.usage ?? null)}`,
96+
)
97+
if (finalMessage.stop_reason === 'max_tokens') {
98+
logger.warn(`[trace:cap] llm.max_tokens model=${req.model} maxTokens=${req.maxTokens} textLen=${textLen}`)
99+
}
87100
return {
88101
stopReason: finalMessage.stop_reason ?? null,
89102
content: cleanedContent,

src/main/orchestrator/agent.ts

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,14 @@ export class Agent {
8888
let finalThinking = ''
8989
let hitIterationLimit = false
9090

91+
logger.info(
92+
`[trace:cap] agent.start name=${this.config.name} model=${this.config.modelId} ` +
93+
`maxIter=${maxIter} maxTokens=${this.config.defaultOptions.maxTokens} tools=${tools.length} msgs=${messages.length}`,
94+
)
95+
9196
for (let iter = 0; iter < maxIter; iter++) {
9297
if (input.signal?.aborted) {
98+
logger.warn(`[trace:cap] agent.abort name=${this.config.name} iter=${iter}`)
9399
throw input.signal.reason ?? new Error('aborted')
94100
}
95101

@@ -117,13 +123,24 @@ export class Agent {
117123
if (thinkingText) finalThinking = thinkingText
118124

119125
// stop_reason 非 tool_use → 终止
120-
if (response.stopReason !== 'tool_use') break
121-
122-
// 执行所有 tool_use 块,追加 tool_result
123126
const toolUses = response.content.filter(
124127
(b): b is Extract<LlmContentBlock, { type: 'tool_use' }> =>
125128
b.type === 'tool_use',
126129
)
130+
logger.info(
131+
`[trace:cap] agent.iter name=${this.config.name} iter=${iter}/${maxIter} ` +
132+
`stop=${response.stopReason ?? 'null'} textLen=${text?.length ?? 0} ` +
133+
`thinkingLen=${thinkingText?.length ?? 0} tools=[${toolUses.map((t) => t.name).join(',')}]`,
134+
)
135+
// max_tokens 常导致「干着干着断了」——正文被硬截断却当 end_turn 处理
136+
if (response.stopReason === 'max_tokens') {
137+
logger.warn(
138+
`[trace:cap] agent.max_tokens name=${this.config.name} iter=${iter} ` +
139+
`textTail=${JSON.stringify((text ?? '').slice(-80))}`,
140+
)
141+
}
142+
143+
if (response.stopReason !== 'tool_use') break
127144

128145
if (limits.maxFunctionCalls && functionCallCount + toolUses.length > limits.maxFunctionCalls) {
129146
logger.warn(`[agent:${this.config.name}] 达 maxFunctionCalls,停止工具调用`)
@@ -135,6 +152,7 @@ export class Agent {
135152
for (const tu of toolUses) {
136153
functionCallCount++
137154
callbacks.onToolCall?.(tu.name, tu.input)
155+
const toolStarted = Date.now()
138156

139157
// —— Handoff 短路(铁律12):handoff_to_X tool 不真执行 ——
140158
// 注入合成 result + 标记 target + 终止循环(MiddlewareTermination 等价)
@@ -146,6 +164,7 @@ export class Agent {
146164
content: JSON.stringify({ handoff_to: handoffTarget }),
147165
is_error: false,
148166
})
167+
logger.info(`[trace:cap] agent.handoff name=${this.config.name}${handoffTarget}`)
149168
continue // 不 executeTool,短路
150169
}
151170

@@ -162,6 +181,10 @@ export class Agent {
162181
},
163182
)
164183
callbacks.onToolResult?.(tu.name, result.content)
184+
logger.info(
185+
`[trace:cap] agent.tool name=${this.config.name} tool=${tu.name} ` +
186+
`err=${result.isError} ms=${Date.now() - toolStarted} resultLen=${result.content.length}`,
187+
)
165188
toolResults.push({
166189
type: 'tool_result',
167190
tool_use_id: tu.id,
@@ -188,7 +211,7 @@ export class Agent {
188211
// 触顶停在 tool_result 之后:强制无工具收尾,让模型基于已有结果给最终答复
189212
if (hitIterationLimit && needsToolResultFinalization(messages) && !input.signal?.aborted) {
190213
logger.warn(
191-
`[agent:${this.config.name}] 达 maxIterations=${maxIter},强制无工具收尾轮`,
214+
`[trace:cap] agent.force_finalize name=${this.config.name} maxIterations=${maxIter}`,
192215
)
193216
const response = await client.stream({
194217
model: this.config.modelId,
@@ -207,8 +230,16 @@ export class Agent {
207230
if (text) finalText = text
208231
const thinkingText = extractThinking(response.content)
209232
if (thinkingText) finalThinking = thinkingText
233+
logger.info(
234+
`[trace:cap] agent.force_finalize.done name=${this.config.name} ` +
235+
`stop=${response.stopReason ?? 'null'} textLen=${text?.length ?? 0}`,
236+
)
210237
}
211238

239+
logger.info(
240+
`[trace:cap] agent.end name=${this.config.name} hitIterLimit=${hitIterationLimit} ` +
241+
`finalTextLen=${finalText.length} textTail=${JSON.stringify(finalText.slice(-60))}`,
242+
)
212243
return { messages, finalText, finalThinking, hitIterationLimit }
213244
}
214245

src/main/orchestrator/home.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -562,13 +562,29 @@ export async function runTeam(
562562
onEvent: (e: HomeStreamEvent) => void,
563563
signal?: AbortSignal,
564564
): Promise<{ output: string }> {
565+
const nodeSummary = graph.nodes
566+
.map((n) => `${n.id}:${n.type}`)
567+
.join(',')
568+
logger.info(
569+
`[trace:cap] runTeam.start session=${sessionId} nodes=[${nodeSummary}] ` +
570+
`edges=${graph.edges.length} inputLen=${inputText.length}`,
571+
)
572+
const t0 = Date.now()
565573
const wf = buildWorkflow(graph, deps)
566574
const result = await runWorkflow(
567575
wf,
568576
{ text: inputText, sessionId },
569-
(e: StreamEvent) => onEvent({ type: 'orch_event', event: e }),
577+
(e: StreamEvent) => {
578+
if (e.type === 'failed' || e.type === 'node_error' || e.type === 'done') {
579+
logger.info(`[trace:cap] runTeam.event type=${e.type} ${JSON.stringify(e).slice(0, 240)}`)
580+
}
581+
onEvent({ type: 'orch_event', event: e })
582+
},
570583
signal,
571584
)
585+
logger.info(
586+
`[trace:cap] runTeam.end session=${sessionId} ms=${Date.now() - t0} outputLen=${result.output.length}`,
587+
)
572588
return result
573589
}
574590

src/main/orchestrator/patterns/agent.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { Executor } from '../models'
99
import { Agent } from '../agent'
1010
import type { AgentConfig } from '@shared/types'
1111
import type { LLMClientOptions } from '../../llm/client'
12+
import { logger } from '../../logger'
1213

1314
// —— Agent 叶子 Executor(§三 D + §三之三 G Sequential)——
1415
// handle 接收消息 → 组装 LlmMessage(strip_tool_blocks_filter 治 2013,
@@ -64,10 +65,17 @@ export class AgentExecutor implements Executor {
6465
onThinking: () => {},
6566
}
6667

68+
logger.info(
69+
`[trace:cap] AgentExecutor.run id=${this.id} msgs=${messages.length} cache=${this.cache.length}`,
70+
)
6771
const result = await this.agent.run(
6872
{ messages, signal: this.agent.deps.toolCtx?.signal },
6973
callbacks,
7074
)
75+
logger.info(
76+
`[trace:cap] AgentExecutor.done id=${this.id} hitIterLimit=${result.hitIterationLimit} ` +
77+
`finalTextLen=${result.finalText.length}`,
78+
)
7179

7280
// 把产出追加到 cache(供下游 fan-out)
7381
this.cache.push({

0 commit comments

Comments
 (0)