44 * SPDX-License-Identifier: Apache-2.0
55 */
66
7+ import { randomUUID } from 'node:crypto' ;
8+ import { readdir , rename , rm } from 'node:fs/promises' ;
9+ import { basename , dirname , join } from 'node:path' ;
710import { redactLogCredentials } from '@qwen-code/acp-bridge/logRedaction' ;
811import { canonicalizeWorkspace } from '@qwen-code/acp-bridge/workspacePaths' ;
912import {
@@ -12,6 +15,7 @@ import {
1215 type PairingRequest ,
1316} from '@qwen-code/channel-base' ;
1417import { resolveChannelCwd } from '../commands/channel/channel-cwd.js' ;
18+ import { daemonChannelStateDir } from '../commands/channel/runtime.js' ;
1519import { getPlugin } from '../commands/channel/channel-registry.js' ;
1620import type {
1721 ChannelSecretUpdate ,
@@ -73,6 +77,28 @@ export interface ChannelPairingRequestsSnapshot {
7377 requests : PairingRequest [ ] ;
7478}
7579
80+ async function removeStagedChannelState ( stateDir : string ) : Promise < void > {
81+ const parent = dirname ( stateDir ) ;
82+ const prefix = `${ basename ( stateDir ) } .deleting-` ;
83+ const entries = await readdir ( parent , { withFileTypes : true } ) . catch (
84+ ( error : unknown ) => {
85+ if ( ( error as NodeJS . ErrnoException ) . code === 'ENOENT' ) return [ ] ;
86+ throw error ;
87+ } ,
88+ ) ;
89+ await Promise . all (
90+ entries
91+ . filter ( ( entry ) => entry . name . startsWith ( prefix ) )
92+ . map ( ( entry ) =>
93+ rm ( join ( parent , entry . name ) , {
94+ recursive : true ,
95+ force : true ,
96+ maxRetries : 3 ,
97+ } ) ,
98+ ) ,
99+ ) ;
100+ }
101+
76102export interface ChannelPairingApprovalResult
77103 extends ChannelPairingRequestsSnapshot {
78104 approved : PairingRequest ;
@@ -443,8 +469,10 @@ export function createChannelManagementService(
443469 } ,
444470 async remove ( name , request ) {
445471 assertManageableInstanceName ( name ) ;
472+ const stateDir = daemonChannelStateDir ( opts . workspaceCwd , name ) ;
446473 const current = opts . store . snapshot ( ) ;
447474 if ( ! Object . hasOwn ( current . channels , name ) ) {
475+ await removeStagedChannelState ( stateDir ) ;
448476 throw new ChannelManagementError (
449477 'channel_instance_not_found' ,
450478 `Channel "${ name } " is not configured in this workspace.` ,
@@ -456,7 +484,22 @@ export function createChannelManagementService(
456484 assertOwnedRuntime ( name ) ;
457485 await stopChannel ( name ) ;
458486 }
459- const persisted = await opts . store . remove ( name , request ) ;
487+ const stagedStateDir = `${ stateDir } .deleting-${ randomUUID ( ) } ` ;
488+ let stagedState = false ;
489+ try {
490+ await rename ( stateDir , stagedStateDir ) ;
491+ stagedState = true ;
492+ } catch ( error ) {
493+ if ( ( error as NodeJS . ErrnoException ) . code !== 'ENOENT' ) throw error ;
494+ }
495+ let persisted : ChannelSettingsSnapshot ;
496+ try {
497+ persisted = await opts . store . remove ( name , request ) ;
498+ } catch ( error ) {
499+ if ( stagedState ) await rename ( stagedStateDir , stateDir ) ;
500+ throw error ;
501+ }
502+ await removeStagedChannelState ( stateDir ) ;
460503 diagnostics . delete ( name ) ;
461504 return resultFor ( name , persisted ) ;
462505 } ,
0 commit comments