(args: LifecycleArgs)
| 173 | ) { |
| 174 | const { executeDocker, allowedDirectories = [] } = options; |
| 175 | const familyGoneTimeoutMs = options.familyGoneTimeoutMs ?? 30_000; |
| 176 | const current = (await listSandboxContainers(executeDocker)).find( |
| 177 | container => container.name === sandbox.name |
| 178 | ); |
| 179 | if (current && current.id !== sandbox.id) |
| 180 | throw new Error(`Container identity changed for ${sandbox.name}`); |
| 181 | let killed: string[]; |
| 182 | if (current && sessionId.startsWith('workspace_') && kiloSessionId !== undefined) { |
| 183 | // Root presence is not exclusive ownership; the control-plane stop proves |
| 184 | // the `exclusive` operation before it kills. The proof can fail merely |
| 185 | // because the runtime was retired or replaced while the container was |
| 186 | // winding down, so re-check the family before treating that as failure. |
| 187 | try { |
| 188 | killed = await stopOwnedControlPlaneSandbox( |
| 189 | sandbox, |
| 190 | kiloSessionId, |
| 191 | executeDocker, |
| 192 | allowedDirectories |
| 193 | ); |
| 194 | } catch (error) { |
| 195 | if (!(await waitForSandboxFamilyGone(sandbox, familyGoneTimeoutMs, executeDocker))) |
| 196 | throw error; |
| 197 | return []; |
| 198 | } |
| 199 | } else { |
| 200 | if (current) { |
| 201 | const owned = await sandboxOwnsSession(current.id, sessionId, kiloSessionId); |
| 202 | if (!owned) throw new Error(`Cannot prove exclusive ownership of ${sandbox.name}`); |
| 203 | } |
| 204 | killed = await killSandboxFamily(sandbox, executeDocker); |
| 205 | } |
| 206 | if (!(await waitForSandboxFamilyGone(sandbox, familyGoneTimeoutMs, executeDocker))) { |
| 207 | throw new Error(`Owned sandbox family ${sandbox.name} is still running after cleanup`); |
| 208 | } |
| 209 | return killed; |
| 210 | } |
| 211 | |
| 212 | /** |
| 213 | * A refused ownership proof is only re-checked this long for a sandbox that is |
| 214 | * winding down on its own; the fault scenarios' 30 s default would stall every |
| 215 | * scenario whose sandbox is not exclusive. |
| 216 | */ |
| 217 | const RECLAIM_FAMILY_GONE_TIMEOUT_MS = 5_000; |
| 218 | const RECLAIM_DOCKER_ATTEMPTS = 3; |
| 219 | const RECLAIM_DOCKER_RETRY_MS = 1_000; |
| 220 | |
| 221 | export type ReclaimSession = { sessionId: string; kiloSessionId?: string }; |
| 222 | |
| 223 | export type SandboxReclaimReport = { |
| 224 | /** Sandbox containers this call stopped. */ |
| 225 | stopped: string[]; |
| 226 | /** Why a session's sandbox was left alone. */ |
| 227 | failures: string[]; |
| 228 | }; |
| 229 | |
| 230 | type LocatedSandbox = { container: SandboxContainer; directory: string | undefined }; |
| 231 | |
| 232 | /** The one primary sandbox a session provably owns, or `null` when none is running. */ |
no test coverage detected