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:
@@ -102,6 +102,7 @@ type FixtureOptions = {
|
||||
uploadFile?: ReturnType<typeof vi.fn>
|
||||
activateAgent?: ReturnType<typeof vi.fn>
|
||||
activateRuntime?: ReturnType<typeof vi.fn>
|
||||
cleanupObsolete?: () => Promise<{ exitCode: number; stdout: string; stderr: string }>
|
||||
pendingOperation?: PendingRemoteEnvironmentOperation
|
||||
}
|
||||
|
||||
@@ -165,7 +166,12 @@ function fixture(options: FixtureOptions = {}) {
|
||||
runAgentLifecycleAction: vi.fn(async (
|
||||
installationId: string,
|
||||
action: string
|
||||
) => (activateAgent as (
|
||||
) => action === 'cleanup-obsolete'
|
||||
? (options.cleanupObsolete ?? (async () => {
|
||||
calls.push('cleanup-obsolete')
|
||||
return { exitCode: 0, stdout: '{"complete":true}', stderr: '' }
|
||||
}))()
|
||||
: (activateAgent as (
|
||||
installationId: string,
|
||||
action: string
|
||||
) => Promise<unknown>)(installationId, action)),
|
||||
@@ -327,6 +333,19 @@ function fixture(options: FixtureOptions = {}) {
|
||||
}
|
||||
|
||||
describe('RemoteEnvironmentPreparer', () => {
|
||||
it.each([0, 2])('reports deferred or unsupported cleanup without failing a healthy update (exit=%s)', async exitCode => {
|
||||
const warning = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
const cleanupObsolete = vi.fn(async () => ({ exitCode, stdout: '{"complete":false}', stderr: '' }))
|
||||
const value = fixture({ cleanupObsolete })
|
||||
try {
|
||||
await expect(value.preparer.prepare(HOST_ID, 'remote-download', undefined, new AbortController().signal)).resolves.toBeUndefined()
|
||||
expect(cleanupObsolete).toHaveBeenCalledOnce()
|
||||
expect(value.regularLease.runAgentLifecycleAction).toHaveBeenLastCalledWith(identity.agent.installationId, 'cleanup-obsolete', expect.any(AbortSignal))
|
||||
expect(warning).toHaveBeenCalledWith(expect.stringContaining('incomplete'))
|
||||
} finally {
|
||||
warning.mockRestore()
|
||||
}
|
||||
})
|
||||
it('runs the remote compound graph in order, verifies identity, cleans explicitly, and emits the resolved method', async () => {
|
||||
const value = fixture()
|
||||
const progress: Array<{ method: string; phase: string }> = []
|
||||
@@ -352,6 +371,7 @@ describe('RemoteEnvironmentPreparer', () => {
|
||||
'commit',
|
||||
'agent',
|
||||
'runtime',
|
||||
'cleanup-obsolete',
|
||||
'cleanup',
|
||||
'remove-operation',
|
||||
'release-bootstrap',
|
||||
|
||||
@@ -406,6 +406,16 @@ export class RemoteEnvironmentPreparer implements Preparer {
|
||||
)
|
||||
signal.throwIfAborted()
|
||||
}
|
||||
try {
|
||||
const cleanup = await lease.runAgentLifecycleAction(installationId, 'cleanup-obsolete', signal)
|
||||
if (cleanup.exitCode !== 0 || JSON.parse(cleanup.stdout).complete !== true) {
|
||||
console.warn('Obsolete Agent cleanup is incomplete; active or unproven installations were retained')
|
||||
}
|
||||
} catch {
|
||||
// Cleanup does not invalidate an adopted environment, including older packages without this action.
|
||||
console.warn('Obsolete Agent cleanup is unavailable; installed payloads were retained')
|
||||
}
|
||||
signal.throwIfAborted()
|
||||
}
|
||||
|
||||
async #prepareUploadedArchive(options: {
|
||||
|
||||
Reference in New Issue
Block a user