refactor: rebuildRuntime 语义化——插件重载透明化,工作区切换全量切换
This commit is contained in:
@@ -82,8 +82,8 @@ async function createWindow(): Promise<void> {
|
||||
})
|
||||
|
||||
// ready-to-show 在 Electron 中可能不止一次触发(实测:回合中途首次挂载受管浏览器
|
||||
// WebContentsView 后会再次发出)。会话初始化只能做一次——二次触发会全量广播
|
||||
// session_switched,把渲染层进行中的回合状态清掉(过程区误折叠/反复横跳的根因)。
|
||||
// WebContentsView 后会再次发出)。会话初始化只做一次——但失败后放开门闩,
|
||||
// 让后续的重复触发承担重试职责(该事件至少会再来一次)。
|
||||
let sessionInitialized = false
|
||||
win.on('ready-to-show', () => {
|
||||
win.show()
|
||||
@@ -91,6 +91,7 @@ async function createWindow(): Promise<void> {
|
||||
sessionInitialized = true
|
||||
// pi 会话初始化放在窗口就绪后,错误可以第一时间在 UI 上呈现
|
||||
agentService.ensureReady().catch((err: unknown) => {
|
||||
sessionInitialized = false
|
||||
console.error('[main] pi session init failed:', err)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -241,8 +241,8 @@ export function registerIpc(): AgentService {
|
||||
// ---- 工具审批 ----
|
||||
ipcMain.handle('approval:resolve', (_event, id: unknown, approved: unknown) => {
|
||||
if (typeof id !== 'string' || typeof approved !== 'boolean') throw new Error('invalid approval')
|
||||
// settle 内部经 onSettle 订阅发出 approval_settled,此处无需重复广播
|
||||
service.approvals.settle(id, approved)
|
||||
service.broadcastEvent({ type: 'approval_settled', id, approved, timedOut: false })
|
||||
return Promise.resolve()
|
||||
})
|
||||
|
||||
|
||||
@@ -199,7 +199,8 @@ export class AgentService {
|
||||
this.settings.workspace = resolved
|
||||
this.saveSettings()
|
||||
this.workspaceDir = resolved
|
||||
await this.rebuildRuntime()
|
||||
// 工作区切换:新 cwd 的最近会话才是目标(不做原会话恢复)
|
||||
await this.rebuildRuntime({ restoreSession: false })
|
||||
}
|
||||
|
||||
private saveSettings(): void {
|
||||
@@ -215,11 +216,6 @@ export class AgentService {
|
||||
return () => this.listeners.delete(fn)
|
||||
}
|
||||
|
||||
/** 供 IPC 层主动广播事件(如审批结果同步到所有窗口) */
|
||||
broadcastEvent(event: AgentUiEvent): void {
|
||||
this.emit(event)
|
||||
}
|
||||
|
||||
private emit(event: AgentUiEvent): void {
|
||||
for (const fn of this.listeners) {
|
||||
try {
|
||||
@@ -1130,11 +1126,26 @@ export class AgentService {
|
||||
}
|
||||
}
|
||||
|
||||
/** 重建运行时(插件重载/启停后由 PluginHost 调用,extensionFactories 重跑) */
|
||||
async rebuildRuntime(): Promise<void> {
|
||||
/** 重建运行时(插件重载/启停、工作区切换时调用,extensionFactories 重跑)。
|
||||
* restoreSession=true(插件重载):重建对渲染层尽量透明——
|
||||
* - 进行中的回合先经 abort 正常结算(事件照常送达,渲染层自行收口),
|
||||
* 再退订/销毁旧运行时——dispose 不含 abort,直接销毁会留下悬空请求;
|
||||
* - 重建走 continueRecent(最近会话):用户开着的不是它时切回原会话;
|
||||
* 原会话是未落盘的空新会话时退回空会话,避免静默接管别的会话;
|
||||
* - 会话未变化时只推轻量 state,不广播 session_switched(该事件语义
|
||||
* 保留给真正的会话变更,全量换血会重挂载渲染层全部消息)。
|
||||
* restoreSession=false(工作区切换):新 cwd 的最近会话即目标状态,
|
||||
* 直接全量广播切换。 */
|
||||
async rebuildRuntime(options?: { restoreSession?: boolean }): Promise<void> {
|
||||
const restoreSession = options?.restoreSession !== false
|
||||
const old = this.runtime
|
||||
this.runtime = null
|
||||
this.initPromise = null
|
||||
if (old?.session.isStreaming) {
|
||||
await old.session.abort().catch(() => undefined)
|
||||
}
|
||||
// abort 结算会落盘当前回合,未持久会话的文件路径此时才物化——在其后读取
|
||||
const previousPath = old?.session.sessionFile ?? null
|
||||
this.unsubscribeAttached?.()
|
||||
this.unsubscribeAttached = null
|
||||
this.attached = null
|
||||
@@ -1143,7 +1154,31 @@ export class AgentService {
|
||||
console.error('[agent-service] runtime dispose error:', err)
|
||||
})
|
||||
}
|
||||
await this.ensureReady()
|
||||
let runtime: AgentSessionRuntime
|
||||
try {
|
||||
runtime = await this.getRuntime()
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
this.emit({ type: 'session_error', message })
|
||||
throw err
|
||||
}
|
||||
if (!restoreSession) {
|
||||
this.emitSwitched(runtime.session)
|
||||
return
|
||||
}
|
||||
const restored = runtime.session.sessionFile ?? ''
|
||||
if (previousPath) {
|
||||
if (!samePath(restored, previousPath) && existsSync(previousPath)) {
|
||||
// 被拽到了最近会话:切回用户原本打开的会话(rebind 发 session_switched,真实变更)
|
||||
await runtime.switchSession(previousPath)
|
||||
return
|
||||
}
|
||||
} else if (runtime.session.messages.length > 0) {
|
||||
// 原会话是未落盘的空新会话:不接管最近会话,回到空会话
|
||||
await runtime.newSession()
|
||||
return
|
||||
}
|
||||
this.emit({ type: 'state', state: this.buildState(runtime.session) })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user