refactor: rebuildRuntime 语义化——插件重载透明化,工作区切换全量切换
This commit is contained in:
@@ -82,8 +82,8 @@ async function createWindow(): Promise<void> {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// ready-to-show 在 Electron 中可能不止一次触发(实测:回合中途首次挂载受管浏览器
|
// ready-to-show 在 Electron 中可能不止一次触发(实测:回合中途首次挂载受管浏览器
|
||||||
// WebContentsView 后会再次发出)。会话初始化只能做一次——二次触发会全量广播
|
// WebContentsView 后会再次发出)。会话初始化只做一次——但失败后放开门闩,
|
||||||
// session_switched,把渲染层进行中的回合状态清掉(过程区误折叠/反复横跳的根因)。
|
// 让后续的重复触发承担重试职责(该事件至少会再来一次)。
|
||||||
let sessionInitialized = false
|
let sessionInitialized = false
|
||||||
win.on('ready-to-show', () => {
|
win.on('ready-to-show', () => {
|
||||||
win.show()
|
win.show()
|
||||||
@@ -91,6 +91,7 @@ async function createWindow(): Promise<void> {
|
|||||||
sessionInitialized = true
|
sessionInitialized = true
|
||||||
// pi 会话初始化放在窗口就绪后,错误可以第一时间在 UI 上呈现
|
// pi 会话初始化放在窗口就绪后,错误可以第一时间在 UI 上呈现
|
||||||
agentService.ensureReady().catch((err: unknown) => {
|
agentService.ensureReady().catch((err: unknown) => {
|
||||||
|
sessionInitialized = false
|
||||||
console.error('[main] pi session init failed:', err)
|
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) => {
|
ipcMain.handle('approval:resolve', (_event, id: unknown, approved: unknown) => {
|
||||||
if (typeof id !== 'string' || typeof approved !== 'boolean') throw new Error('invalid approval')
|
if (typeof id !== 'string' || typeof approved !== 'boolean') throw new Error('invalid approval')
|
||||||
|
// settle 内部经 onSettle 订阅发出 approval_settled,此处无需重复广播
|
||||||
service.approvals.settle(id, approved)
|
service.approvals.settle(id, approved)
|
||||||
service.broadcastEvent({ type: 'approval_settled', id, approved, timedOut: false })
|
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -199,7 +199,8 @@ export class AgentService {
|
|||||||
this.settings.workspace = resolved
|
this.settings.workspace = resolved
|
||||||
this.saveSettings()
|
this.saveSettings()
|
||||||
this.workspaceDir = resolved
|
this.workspaceDir = resolved
|
||||||
await this.rebuildRuntime()
|
// 工作区切换:新 cwd 的最近会话才是目标(不做原会话恢复)
|
||||||
|
await this.rebuildRuntime({ restoreSession: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
private saveSettings(): void {
|
private saveSettings(): void {
|
||||||
@@ -215,11 +216,6 @@ export class AgentService {
|
|||||||
return () => this.listeners.delete(fn)
|
return () => this.listeners.delete(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 供 IPC 层主动广播事件(如审批结果同步到所有窗口) */
|
|
||||||
broadcastEvent(event: AgentUiEvent): void {
|
|
||||||
this.emit(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
private emit(event: AgentUiEvent): void {
|
private emit(event: AgentUiEvent): void {
|
||||||
for (const fn of this.listeners) {
|
for (const fn of this.listeners) {
|
||||||
try {
|
try {
|
||||||
@@ -1130,11 +1126,26 @@ export class AgentService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重建运行时(插件重载/启停后由 PluginHost 调用,extensionFactories 重跑) */
|
/** 重建运行时(插件重载/启停、工作区切换时调用,extensionFactories 重跑)。
|
||||||
async rebuildRuntime(): Promise<void> {
|
* 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
|
const old = this.runtime
|
||||||
this.runtime = null
|
this.runtime = null
|
||||||
this.initPromise = 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?.()
|
||||||
this.unsubscribeAttached = null
|
this.unsubscribeAttached = null
|
||||||
this.attached = null
|
this.attached = null
|
||||||
@@ -1143,7 +1154,31 @@ export class AgentService {
|
|||||||
console.error('[agent-service] runtime dispose error:', err)
|
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