diff --git a/packages/runtimeuse/package-lock.json b/packages/runtimeuse/package-lock.json index dce635e..a61bdd3 100644 --- a/packages/runtimeuse/package-lock.json +++ b/packages/runtimeuse/package-lock.json @@ -1,12 +1,12 @@ { "name": "runtimeuse", - "version": "0.11.0", + "version": "0.11.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "runtimeuse", - "version": "0.11.0", + "version": "0.11.1", "license": "FSL", "dependencies": { "@anthropic-ai/claude-agent-sdk": "^0.2.73", diff --git a/packages/runtimeuse/package.json b/packages/runtimeuse/package.json index 5df2767..792d558 100644 --- a/packages/runtimeuse/package.json +++ b/packages/runtimeuse/package.json @@ -1,6 +1,6 @@ { "name": "runtimeuse", - "version": "0.11.0", + "version": "0.11.1", "description": "AI agent runtime with WebSocket protocol, artifact handling, and secret management", "license": "FSL", "type": "module", diff --git a/packages/runtimeuse/src/session.ts b/packages/runtimeuse/src/session.ts index d0e0fa2..e4397ae 100644 --- a/packages/runtimeuse/src/session.ts +++ b/packages/runtimeuse/src/session.ts @@ -36,6 +36,7 @@ export class WebSocketSession { private requestInFlight = false; private secrets: string[] = []; private logger: Logger; + private drainPromise: Promise | null = null; constructor(ws: WebSocket, config: SessionConfig) { this.ws = ws; @@ -68,22 +69,7 @@ export class WebSocketSession { } this.logger.log("WebSocket connection closed"); this.currentAbortController?.abort(); - - // Give chokidar time to observe files the agent wrote right before - // the session ended. Without this, late `add` events would not fire - // before we stop the watcher, and those artifacts would be lost. - const delayMs = this.config.postInvocationDelayMs ?? 3_000; - if (this.artifactManager && delayMs > 0) { - this.logger.log(`Waiting ${delayMs}ms for artifact watcher to drain...`); - await sleep(delayMs); - } - await this.artifactManager?.stopWatching(); - await this.artifactManager?.waitForPendingRequests( - this.config.artifactWaitMs ?? 60_000, - ); - await this.config.uploadTracker.waitForAll( - this.config.uploadTimeoutMs ?? 30_000, - ); + await this.drain(); resolve(); }); @@ -97,6 +83,9 @@ export class WebSocketSession { switch (message.message_type) { case "end_session_message": this.logger.log("Received end_session_message. Closing session."); + // Drain the artifact watcher *before* closing so any late chokidar + // events still have an open socket to send upload requests through. + await this.drain(); this.ws.close(); return; @@ -211,6 +200,26 @@ export class WebSocketSession { } } + private drain(): Promise { + if (!this.drainPromise) { + this.drainPromise = (async () => { + const delayMs = this.config.postInvocationDelayMs ?? 3_000; + if (this.artifactManager && delayMs > 0) { + this.logger.log(`Waiting ${delayMs}ms for artifact watcher to drain...`); + await sleep(delayMs); + } + await this.artifactManager?.stopWatching(); + await this.artifactManager?.waitForPendingRequests( + this.config.artifactWaitMs ?? 60_000, + ); + await this.config.uploadTracker.waitForAll( + this.config.uploadTimeoutMs ?? 30_000, + ); + })(); + } + return this.drainPromise; + } + private ensureArtifactManager(): void { if (this.artifactManager) { this.artifactManager.setLogger(this.logger);