/** Mock SDK transport: tests host replay lifecycle, NOT SDK permission matching. */ import assert from 'node:assert/strict' import { mkdtemp, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { ClaudeKernel } from '../src/main/agent/claude/claude-kernel' import { rememberSessionGrant, sessionGrantProposal, clearSessionGrants } from '../src/main/agent/claude/session-grants' const temp=await mkdtemp(join(tmpdir(),'grant-replay-unit-'));process.env.CLAUDE_TEST_USER_DATA=temp const instances:any[]=[] let fail=true ;(globalThis as any).__SDK_QUERY_FIXTURE=(input:any)=>{ let end!:()=>void;const done=new Promise(resolve=>{end=resolve}) const instance={input,closed:false,settings:[] as any[],async *[Symbol.asyncIterator](){await done},close(){this.closed=true;end()},async applyFlagSettings(value:any){this.settings.push(value);if(fail)throw new Error('fixture replay failure')},async setPermissionMode(){},async getContextUsage(){return {categories:[]}}} instances.push(instance);return instance } const model={profile:{id:'fixture',model:'fixture',authMode:'api-key'},config:{model:'fixture',baseUrl:'http://127.0.0.1:1',apiKey:'not-used',authMode:'api-key'}} const kernel=new ClaudeKernel({sessionId:'a',workspaceRoot:temp,model:model as any,permissionMode:'default-sandbox',agentMode:'craft',onSessionId:()=>{}}) try{ const grant=sessionGrantProposal([{type:'addRules',behavior:'allow',destination:'session',rules:[{toolName:'Bash',ruleContent:'echo safe:*'}]}])! rememberSessionGrant('a',grant,temp) await assert.rejects(kernel.ensureStarted(),/恢复本会话授权失败/) assert.equal(instances.length,1);assert.equal(instances[0].closed,true) assert.equal((kernel as any).instance,null);assert.equal((kernel as any).input,null) // Complete retired iterator cleanup before attempting a second kernel start. await (kernel as any).draining await assert.rejects(kernel.ensureStarted(),/恢复本会话授权失败/) assert.equal(instances.length,2,'failure never silently reuses poisoned query') await (kernel as any).draining fail=false;await kernel.ensureStarted() assert.equal(instances.length,3) assert.equal((kernel as any).draining,null,'retired consumer draining lock cleared') assert.deepEqual(await kernel.getContextSummary(),{categories:[]},'context queries recover after failed replay') const raw=kernel as any raw.active={settled:false,appends:new Map(),groupIds:['original'],request:{signal:new AbortController().signal,onSegmentStart:async()=>{},onSegmentComplete:async()=>{}}} const append=kernel.appendInput({userMessageId:'append',prompt:'fixture'}) assert.equal(raw.active.appends.size,1,'append accepted after retry, no stale draining lock') raw.active.appends.get('append').resolve();await append raw.active=null assert.deepEqual(instances[2].settings,[{permissions:{allow:['Bash(echo safe:*)']}}]) const hook=instances[2].input.options.hooks.PreToolUse[0].hooks[0] ;(kernel as any).active={request:{signal:new AbortController().signal}} await kernel.setPolicy('full-access','plan') const denied=await hook({hook_event_name:'PreToolUse',tool_name:'Bash',tool_input:{command:'echo safe'}}) assert.equal(denied.hookSpecificOutput.permissionDecision,'deny','plan denies even matching replay grant') ;(kernel as any).active=null const other=new ClaudeKernel({sessionId:'other',workspaceRoot:temp,model:model as any,permissionMode:'default-sandbox',agentMode:'craft',onSessionId:()=>{}}) await other.ensureStarted();assert.equal(instances.at(-1).settings.length,0,'other session receives no grants') await other.close() console.log('Mock SDK replay lifecycle passed: fail closed, retry, session isolation, independent mode guard') }finally{(kernel as any).active=null;await kernel.close();clearSessionGrants();await rm(temp,{recursive:true,force:true})}