fix: restore remote conversation recovery and clean obsolete agents
Wait for model delivery acknowledgements before completing remote prompts and preserve completed outcomes when idle Runtime cleanup fails. Explicit messages after an unknown outcome now open a fresh binding with history instead of reusing the failed session; recovery remains attach-only. Run obsolete payload cleanup through the current Agent after activation, retaining history and unresolved process ownership. Live legacy daemons and shared Runtime versions remain deferred. Includes focused regressions, local OpenCode simulation, and isolated Linux validation evidence. Typecheck and lint passed; the full test run timed out. Release note: Remote conversations can continue after an uncertain result, and completed answers no longer fail solely because Runtime cleanup fails. Updates remove verified inactive Agent payloads and owned orphan processes while retaining active or uncertain installations and conversation history.
This commit is contained in:
@@ -615,6 +615,58 @@ describe('model bridge loopback helper', () => {
|
||||
describe('model bridge Unix broker', () => {
|
||||
const runOnUnix = process.platform === 'win32' ? it.skip : it
|
||||
|
||||
runOnUnix.each(['delivered', 'failed', 'forced', 'timeout'] as const)(
|
||||
'settles a flushed HTTP response before completion close (%s)', async outcome => {
|
||||
let releaseAck!: () => void
|
||||
const ackGate = new Promise<void>(resolve => { releaseAck = resolve })
|
||||
let enteredAck!: () => void
|
||||
const ackEntered = new Promise<void>(resolve => { enteredAck = resolve })
|
||||
const failDelivery = vi.fn()
|
||||
const broker = new ModelBridgeBrokerServer({
|
||||
scratchDirectory: privateTemporaryDirectory(),
|
||||
dispatch: async () => ({
|
||||
response: validResponse,
|
||||
acknowledgeDelivery: async () => {
|
||||
enteredAck()
|
||||
await ackGate
|
||||
if (outcome === 'failed') throw new Error('delivery failed')
|
||||
},
|
||||
failDelivery
|
||||
})
|
||||
})
|
||||
await broker.listen()
|
||||
const proxy = new ModelBridgeLoopbackProxy({ exchange: createUnixModelBridgeExchange({ socketPath: broker.socketPath }) })
|
||||
const origin = await proxy.listen()
|
||||
try {
|
||||
const response = await sendHttp(origin, { path: '/v1/messages', body: '{}' })
|
||||
expect(response.body).toBe('{"ok":true}')
|
||||
await ackEntered
|
||||
let closed = false
|
||||
const close = broker.close({ waitForDelivery: outcome !== 'forced' }).then(() => { closed = true })
|
||||
// An event-loop turn proves completion close has not torn down the ACK socket.
|
||||
await new Promise<void>(resolve => setImmediate(resolve))
|
||||
if (outcome === 'forced') {
|
||||
await close
|
||||
expect(failDelivery).toHaveBeenCalledOnce()
|
||||
} else if (outcome === 'timeout') {
|
||||
await expect(close).rejects.toThrow()
|
||||
expect(failDelivery).toHaveBeenCalledOnce()
|
||||
} else {
|
||||
expect(closed).toBe(false)
|
||||
expect(failDelivery).not.toHaveBeenCalled()
|
||||
releaseAck()
|
||||
await close
|
||||
expect(failDelivery).toHaveBeenCalledTimes(outcome === 'failed' ? 1 : 0)
|
||||
}
|
||||
expect(existsSync(broker.socketPath)).toBe(false)
|
||||
} finally {
|
||||
releaseAck()
|
||||
await proxy.close()
|
||||
await broker.close()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
runOnUnix('routes shared Sessions concurrently and rejects queued work after its operation ends', async () => {
|
||||
let unblock!: () => void
|
||||
const blocked = new Promise<void>(resolve => { unblock = resolve })
|
||||
|
||||
Reference in New Issue
Block a user