Compare commits

..

5 Commits

Author SHA1 Message Date
claude-code-best
6b205f5798 chore: 2.6.11 2026-06-05 10:50:07 +08:00
claude-code-best
7e3d825f0e fix: ACP prompt 未切换全局 sessionId 导致 transcript 写入错误会话文件
prompt() 在调用 submitMessage 前没有 switchSession,recordTranscript
依赖全局 getSessionId() 确定写入路径,多会话场景下新会话内容会覆盖旧会话。
2026-06-05 10:49:37 +08:00
claude-code-best
a077ec8d85 fix: ACP 模式下文本重复显示 — 流式事件与助手消息双重推送
stream_event 和 assistant 消息对同一文本内容各发一次 agent_message_chunk,
导致 ACP 客户端显示两遍。添加 streamingActive 标志,在收到 stream_event 后
过滤掉 assistant 消息中已被流式路径处理的 text/thinking 块。
2026-06-05 10:37:59 +08:00
claude-code-best
55a932df68 chore: 2.6.10 2026-06-05 00:02:54 +08:00
claude-code-best
230eb489b5 fix: ACP 模式加载 agent 定义并透传 subagent 层级信息
- agent.ts: session 创建时调用 getAgentDefinitionsWithOverrides 加载内置
  subagent(Explore/Plan/General-Purpose 等),注入 appState 和 engineConfig
- bridge.ts: assistantMessageToAcpNotifications 调用时补上 parentToolUseId,
  使 subagent 内部工具调用的 _meta 中携带父级标记
2026-06-05 00:02:21 +08:00
4 changed files with 39 additions and 8 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "claude-code-best",
"version": "2.6.9",
"version": "2.6.11",
"description": "Reverse-engineered Anthropic Claude Code CLI — interactive AI coding assistant in the terminal",
"type": "module",
"author": "claude-code-best <claude-code-best@proton.me>",

View File

@@ -1226,19 +1226,20 @@ describe('AcpAgent', () => {
)
})
test('prompt does not trigger additional switchSession for multi-session', async () => {
test('prompt switches global sessionId to the correct session', async () => {
const agent = new AcpAgent(makeConn())
await agent.newSession({ cwd: '/tmp' } as any)
await agent.newSession({ cwd: '/tmp' } as any)
mockSwitchSession.mockClear()
// Prompts should not call switchSession — alignment happens at session creation
// Prompts must switch global state so recordTranscript writes to
// the correct session file in multi-session scenarios.
const s1 = agent.sessions.keys().next().value
await agent.prompt({
sessionId: s1,
prompt: [{ type: 'text', text: 'hello' }],
} as any)
expect(mockSwitchSession).not.toHaveBeenCalled()
expect(mockSwitchSession).toHaveBeenCalledWith(s1, null)
})
})
})

View File

@@ -54,6 +54,7 @@ import { getEmptyToolPermissionContext } from '../../Tool.js'
import type { PermissionMode } from '../../types/permissions.js'
import type { Command } from '../../types/command.js'
import { getCommands } from '../../commands.js'
import { getAgentDefinitionsWithOverrides } from '@claude-code-best/builtin-tools/tools/AgentTool/loadAgentsDir.js'
import {
setOriginalCwd,
switchSession,
@@ -299,6 +300,10 @@ export class AcpAgent implements Agent {
// After a previous interrupt(), the internal controller is stuck in
// aborted state — without this, submitMessage() fails immediately.
session.queryEngine.resetAbortController()
// Switch global session state so recordTranscript writes to the correct
// session file. Without this, multi-session scenarios (or creating a new
// session after another) write transcript data to the wrong file.
switchSession(params.sessionId as SessionId, getSessionProjectDir())
const sdkMessages = session.queryEngine.submitMessage(promptInput)
@@ -549,8 +554,14 @@ export class AcpAgent implements Agent {
},
}
// Load commands for slash command and skill support
const commands = await getCommands(cwd)
// Load commands and agent definitions for subagent support
const [commands, agentDefinitionsResult] = await Promise.all([
getCommands(cwd),
getAgentDefinitionsWithOverrides(cwd),
])
// Inject agent definitions into appState
appState.agentDefinitions = agentDefinitionsResult
// Build QueryEngine config
const engineConfig: QueryEngineConfig = {
@@ -558,7 +569,7 @@ export class AcpAgent implements Agent {
tools,
commands,
mcpClients: [],
agents: [],
agents: agentDefinitionsResult.activeAgents,
canUseTool,
getAppState: () => appState,
setAppState: (updater: (prev: AppState) => AppState) => {

View File

@@ -633,6 +633,7 @@ export async function forwardSessionUpdates(
let lastAssistantTotalUsage: number | null = null
let lastAssistantModel: string | null = null
let lastContextWindowSize = 200000
let streamingActive = false
try {
while (!abortSignal.aborted) {
@@ -788,6 +789,7 @@ export async function forwardSessionUpdates(
for (const notification of notifications) {
await conn.sessionUpdate(notification)
}
streamingActive = true
break
}
@@ -827,6 +829,8 @@ export async function forwardSessionUpdates(
{
clientCapabilities,
cwd,
parentToolUseId,
streamingActive,
},
)
for (const notification of notifications) {
@@ -942,6 +946,7 @@ function assistantMessageToAcpNotifications(
clientCapabilities?: ClientCapabilities
parentToolUseId?: string | null
cwd?: string
streamingActive?: boolean
},
): SessionNotification[] {
const message = msg.message as Record<string, unknown> | undefined
@@ -966,8 +971,20 @@ function assistantMessageToAcpNotifications(
]
}
// When streaming is active, text/thinking were already sent via stream_event
// messages. Filter them out to avoid duplicate agent_message_chunk /
// agent_thought_chunk notifications. String content (synthetic messages)
// is unaffected — those have no corresponding stream_events.
const contentToProcess = options?.streamingActive
? content.filter(
block => block.type !== 'text' && block.type !== 'thinking',
)
: content
if (contentToProcess.length === 0) return []
return toAcpNotifications(
content,
contentToProcess,
'assistant',
sessionId,
toolUseCache,
@@ -987,6 +1004,7 @@ function streamEventToAcpNotifications(
options?: {
clientCapabilities?: ClientCapabilities
cwd?: string
streamingActive?: boolean
},
): SessionNotification[] {
const event = (msg as unknown as { event: Record<string, unknown> }).event
@@ -1055,6 +1073,7 @@ function toAcpNotifications(
clientCapabilities?: ClientCapabilities
parentToolUseId?: string | null
cwd?: string
streamingActive?: boolean
},
): SessionNotification[] {
const output: SessionNotification[] = []