@@ -9,7 +9,7 @@ import type {
99 CloudArtifactProvider ,
1010} from '@agent-device/contracts/observability' ;
1111import type { DaemonRequest , DaemonResponse } from '../types.ts' ;
12- import type { LeaseRegistry } from '../lease-registry.ts' ;
12+ import type { LeaseRegistry , ReleaseLeaseRequest } from '../lease-registry.ts' ;
1313import type { SessionStore } from '../session-store.ts' ;
1414import {
1515 isProxyLeaseScope ,
@@ -21,7 +21,7 @@ import {
2121 leaseScopeToHeartbeatRequest ,
2222 leaseScopeToReleaseRequest ,
2323} from '../../core/lease-scope.ts' ;
24- import { AppError , createRequestCanceledError } from '@agent-device/kernel/errors' ;
24+ import { AppError , createRequestCanceledError , errorMessage } from '@agent-device/kernel/errors' ;
2525import { LEASE_ALLOCATION_BUDGET_MS } from '../../core/command-descriptor/timeout-policy.ts' ;
2626import { getRequestSignal , isRequestCanceled } from '../../request/cancel.ts' ;
2727import { listDownloadableArtifacts } from '../artifact-tracking.ts' ;
@@ -75,7 +75,7 @@ export async function handleLeaseCommands(args: LeaseHandlerArgs): Promise<Daemo
7575 deadline : Date . now ( ) + LEASE_ALLOCATION_BUDGET_MS ,
7676 } ) ;
7777 } catch ( error ) {
78- releaseRegistryLease ( leaseRegistry , lease ) ;
78+ leaseRegistry . releaseLease ( leaseReleaseRequestFor ( lease ) ) ;
7979 throw error ;
8080 }
8181 if ( isRequestCanceled ( req . meta ?. requestId ) ) {
@@ -101,93 +101,103 @@ export async function handleLeaseCommands(args: LeaseHandlerArgs): Promise<Daemo
101101 }
102102 case 'lease_release' : {
103103 const releaseRequest = leaseScopeToReleaseRequest ( leaseScope ) ;
104- const lease = leaseRegistry . getLease ( releaseRequest ) ;
105- const providerData = lease
106- ? await leaseLifecycleProvider ?. release ?.( lease , leaseLifecycleContext ( req ) )
107- : undefined ;
108- const result = leaseRegistry . releaseLease ( releaseRequest ) ;
104+ const outcome = await releaseLease (
105+ leaseRegistry ,
106+ leaseLifecycleProvider ,
107+ leaseRegistry . getLease ( releaseRequest ) ,
108+ releaseRequest ,
109+ leaseLifecycleContext ( req ) ,
110+ ) ;
109111 return {
110112 ok : true ,
111- data : { released : result . released , ...( providerData ? { provider : providerData } : { } ) } ,
113+ data : {
114+ released : outcome . released ,
115+ ...( outcome . provider ? { provider : outcome . provider } : { } ) ,
116+ } ,
112117 } ;
113118 }
114119 default :
115120 return null ;
116121 }
117122}
118123
119- function releaseRegistryLease ( leaseRegistry : LeaseRegistry , lease : DeviceLease ) : void {
120- leaseRegistry . releaseLease (
121- leaseScopeToReleaseRequest ( {
122- leaseId : lease . leaseId ,
123- tenantId : lease . tenantId ,
124- runId : lease . runId ,
125- leaseBackend : lease . backend ,
126- leaseProvider : lease . leaseProvider ,
127- deviceKey : lease . deviceKey ,
128- clientId : lease . clientId ,
129- } ) ,
130- ) ;
124+ function leaseReleaseRequestFor ( lease : DeviceLease ) : ReleaseLeaseRequest {
125+ return leaseScopeToReleaseRequest ( {
126+ leaseId : lease . leaseId ,
127+ tenantId : lease . tenantId ,
128+ runId : lease . runId ,
129+ leaseBackend : lease . backend ,
130+ leaseProvider : lease . leaseProvider ,
131+ deviceKey : lease . deviceKey ,
132+ clientId : lease . clientId ,
133+ } ) ;
134+ }
135+
136+ type LeaseReleaseOutcome = {
137+ /** The daemon's own lease record was released. */
138+ released : boolean ;
139+ /** Provider release data (providerSessionId, warnings, cloudArtifacts…) when a provider held it. */
140+ provider ?: Record < string , unknown > ;
141+ } ;
142+
143+ /** THE release path: provider first (it still needs the lease record), then the registry. */
144+ async function releaseLease (
145+ leaseRegistry : LeaseRegistry ,
146+ leaseLifecycleProvider : LeaseLifecycleProvider | undefined ,
147+ lease : DeviceLease | undefined ,
148+ request : ReleaseLeaseRequest ,
149+ context ?: LeaseLifecycleContext ,
150+ ) : Promise < LeaseReleaseOutcome > {
151+ const provider = lease ? await leaseLifecycleProvider ?. release ?.( lease , context ) : undefined ;
152+ return { released : leaseRegistry . releaseLease ( request ) . released , provider } ;
131153}
132154
133155/**
134- * Releases a lease that finished allocating after its requester was gone, and
135- * turns the outcome into the canceled-request error the (absent) requester
136- * would have received. Release evidence is only claimed on a clean release: a
137- * provider that could not delete its session (`warnings`) is reported as such,
138- * with the identifiers an operator needs to stop it by hand.
156+ * Releases a lease that finished allocating after its requester was gone and
157+ * turns the outcome into the canceled-request error nobody is left to receive:
158+ * a throwing provider release is folded into `releaseError` rather than raised,
159+ * and the provider session counts as released only when it reported no
160+ * warnings — otherwise the error names what an operator must stop by hand.
139161 */
140162async function releaseAllocationForGoneRequester (
141163 lease : DeviceLease ,
142164 leaseLifecycleProvider : LeaseLifecycleProvider | undefined ,
143165 leaseRegistry : LeaseRegistry ,
144166) : Promise < AppError > {
145- const outcome = await releaseProviderLease ( lease , leaseLifecycleProvider ) ;
146- releaseRegistryLease ( leaseRegistry , lease ) ;
147- return canceledAllocationError ( lease , outcome ) ;
148- }
149-
150- type ProviderReleaseOutcome = {
151- providerSessionId ?: unknown ;
152- warnings : unknown [ ] ;
153- releaseError ?: string ;
154- } ;
155-
156- async function releaseProviderLease (
157- lease : DeviceLease ,
158- leaseLifecycleProvider : LeaseLifecycleProvider | undefined ,
159- ) : Promise < ProviderReleaseOutcome > {
167+ const request = leaseReleaseRequestFor ( lease ) ;
168+ let outcome : LeaseReleaseOutcome ;
169+ let releaseError : string | undefined ;
160170 try {
161- const released = await leaseLifecycleProvider ?. release ?.( lease ) ;
162- return {
163- providerSessionId : released ?. providerSessionId ,
164- warnings : Array . isArray ( released ?. warnings ) ? released . warnings : [ ] ,
165- } ;
171+ outcome = await releaseLease ( leaseRegistry , leaseLifecycleProvider , lease , request ) ;
166172 } catch ( error ) {
167- return { warnings : [ ] , releaseError : errorMessage ( error ) } ;
173+ releaseError = errorMessage ( error ) ;
174+ outcome = { released : leaseRegistry . releaseLease ( request ) . released } ;
168175 }
176+ return canceledAllocationError ( lease , outcome , releaseError ) ;
169177}
170178
171- function canceledAllocationError ( lease : DeviceLease , outcome : ProviderReleaseOutcome ) : AppError {
172- const { providerSessionId, warnings, releaseError } = outcome ;
173- const released = releaseError === undefined && warnings . length === 0 ;
179+ function canceledAllocationError (
180+ lease : DeviceLease ,
181+ outcome : LeaseReleaseOutcome ,
182+ releaseError : string | undefined ,
183+ ) : AppError {
184+ const providerSessionId = outcome . provider ?. providerSessionId ;
185+ const warnings = Array . isArray ( outcome . provider ?. warnings ) ? outcome . provider . warnings : [ ] ;
186+ const providerReleased = releaseError === undefined && warnings . length === 0 ;
174187 return createRequestCanceledError ( {
175188 leaseId : lease . leaseId ,
176189 leaseProvider : lease . leaseProvider ,
190+ released : outcome . released ,
191+ providerReleased,
177192 providerSessionId,
178193 ...( warnings . length > 0 ? { warnings } : { } ) ,
179194 ...( releaseError !== undefined ? { releaseError } : { } ) ,
180- released,
181- hint : released
195+ hint : providerReleased
182196 ? 'The lease request was canceled while the provider was still allocating; the session it produced was released.'
183197 : `The lease request was canceled while the provider was still allocating, and the session it produced could NOT be confirmed released — it may still be running and billing. Stop provider session ${ String ( providerSessionId ?? '(unknown)' ) } for lease ${ lease . leaseId } by hand.` ,
184198 } ) ;
185199}
186200
187- function errorMessage ( error : unknown ) : string {
188- return error instanceof Error ? error . message : String ( error ) ;
189- }
190-
191201function leaseLifecycleContext ( req : DaemonRequest ) : LeaseLifecycleContext {
192202 return {
193203 flags : req . flags ,
0 commit comments