重构会话agent连接、初始化、加载配置流程

This commit is contained in:
xintaofei
2026-03-26 19:55:28 +08:00
parent 484cb3557a
commit d1eaa8f725
17 changed files with 154 additions and 28 deletions

View File

@@ -187,6 +187,12 @@ type Action =
supported: boolean
}
| { type: "MODE_CHANGED"; contextKey: string; modeId: string }
| {
type: "CONFIG_OPTION_CHANGED"
contextKey: string
configId: string
valueId: string
}
| {
type: "PLAN_UPDATE"
contextKey: string
@@ -213,6 +219,21 @@ const MAX_LIVE_TOOL_RAW_OUTPUT_CHARS = 200_000
const MAX_BUFFERED_UNMAPPED_EVENTS_PER_CONNECTION = 64
const MAX_BUFFERED_UNMAPPED_CONNECTIONS = 128
// Per-agentType cache for selectors (modes / configOptions).
// Populated when real data arrives from the backend.
// Used as UI-layer fallback when the connection hasn't received real data yet.
const selectorsCache = new Map<
string,
{
modes: SessionModeStateInfo | null
configOptions: SessionConfigOptionInfo[] | null
}
>()
export function getCachedSelectors(agentType: string) {
return selectorsCache.get(agentType) ?? null
}
function clampLiveRawOutput(output: string | null): string | null {
if (typeof output !== "string") return output
if (output.length <= MAX_LIVE_TOOL_RAW_OUTPUT_CHARS) return output
@@ -923,6 +944,33 @@ function connectionsReducer(
return next
}
case "CONFIG_OPTION_CHANGED": {
const conn = state.get(action.contextKey)
if (!conn) return state
const options =
conn.configOptions ??
selectorsCache.get(conn.agentType)?.configOptions ??
null
if (!options) return state
const idx = options.findIndex((o) => o.id === action.configId)
if (idx === -1) return state
const opt = options[idx]
if (
opt.kind.type !== "select" ||
opt.kind.current_value === action.valueId
) {
return state
}
const updated = [...options]
updated[idx] = {
...opt,
kind: { ...opt.kind, current_value: action.valueId },
}
const next = new Map(state)
next.set(action.contextKey, { ...conn, configOptions: updated })
return next
}
case "PLAN_UPDATE": {
const conn = state.get(action.contextKey)
if (!conn?.liveMessage) return state
@@ -1472,29 +1520,59 @@ export function AcpConnectionsProvider({ children }: { children: ReactNode }) {
sessionId: e.session_id,
})
break
case "session_modes":
case "session_modes": {
flushStreamingQueue()
const modeConn = storeRef.current.connections.get(contextKey)
dispatch({
type: "SESSION_MODES",
contextKey,
modes: e.modes,
})
if (modeConn) {
const entry = selectorsCache.get(modeConn.agentType) ?? {
modes: null,
configOptions: null,
}
entry.modes = e.modes
selectorsCache.set(modeConn.agentType, entry)
}
break
case "session_config_options":
}
case "session_config_options": {
flushStreamingQueue()
const cfgConn = storeRef.current.connections.get(contextKey)
dispatch({
type: "SESSION_CONFIG_OPTIONS",
contextKey,
configOptions: e.config_options,
})
if (cfgConn) {
const entry = selectorsCache.get(cfgConn.agentType) ?? {
modes: null,
configOptions: null,
}
entry.configOptions = e.config_options
selectorsCache.set(cfgConn.agentType, entry)
}
break
case "selectors_ready":
}
case "selectors_ready": {
flushStreamingQueue()
dispatch({
type: "SELECTORS_READY",
contextKey,
})
// Cache for agent types that may not emit session_modes /
// session_config_options at all (no selectors).
const rdyConn = storeRef.current.connections.get(contextKey)
if (rdyConn && !selectorsCache.has(rdyConn.agentType)) {
selectorsCache.set(rdyConn.agentType, {
modes: rdyConn.modes,
configOptions: rdyConn.configOptions,
})
}
break
}
case "prompt_capabilities":
flushStreamingQueue()
dispatch({
@@ -1886,10 +1964,16 @@ export function AcpConnectionsProvider({ children }: { children: ReactNode }) {
async (contextKey: string, configId: string, valueId: string) => {
const conn = storeRef.current.connections.get(contextKey)
if (!conn) return
dispatch({
type: "CONFIG_OPTION_CHANGED",
contextKey,
configId,
valueId,
})
lastActivityRef.current.set(contextKey, Date.now())
await acpSetConfigOption(conn.connectionId, configId, valueId)
},
[]
[dispatch]
)
const cancel = useCallback(async (contextKey: string) => {