@@ -920,6 +920,114 @@ describe("ClaudeAdapterV2 session permissions", () => {
920920 } ) ;
921921} ) ;
922922
923+ describe ( "ClaudeAdapterV2 Auto-accept edits" , ( ) => {
924+ it . effect ( "asks before a command instead of allowing it" , ( ) =>
925+ Effect . scoped (
926+ Effect . gen ( function * ( ) {
927+ const fileSystem = yield * FileSystem . FileSystem ;
928+ const idAllocator = yield * IdAllocatorV2 ;
929+ const attachmentsDir = yield * fileSystem . makeTempDirectoryScoped ( {
930+ prefix : "t3-claude-accept-edits-" ,
931+ } ) ;
932+ let openedOptions : ClaudeAgentSdkQueryOptions | undefined ;
933+ const adapter = makeClaudeAdapterV2 ( {
934+ instanceId : CLAUDE_DEFAULT_INSTANCE_ID ,
935+ settings : DEFAULT_CLAUDE_SETTINGS ,
936+ environment : { } ,
937+ attachmentsDir,
938+ fileSystem,
939+ path : yield * Path . Path ,
940+ idAllocator,
941+ queryRunner : {
942+ allocateSessionId : Effect . succeed ( "native-thread-claude-accept-edits" ) ,
943+ open : ( input ) =>
944+ Effect . sync ( ( ) => {
945+ openedOptions = input . options ;
946+ return {
947+ messages : Stream . never ,
948+ offer : ( ) => Effect . void ,
949+ setModel : ( ) => Effect . void ,
950+ interrupt : Effect . void ,
951+ close : Effect . void ,
952+ } ;
953+ } ) ,
954+ forkSession : ( ) => Effect . die ( "unused" ) ,
955+ subagentLaunchToolUseId : ( ) => Effect . succeed ( null ) ,
956+ assertComplete : Effect . void ,
957+ } ,
958+ } ) ;
959+ const runtimePolicy = ProviderAdapterV2RuntimePolicy . make ( {
960+ runtimeMode : "auto-accept-edits" ,
961+ interactionMode : "default" ,
962+ cwd : "/workspace" ,
963+ } ) ;
964+ const threadId = ThreadId . make ( "thread-claude-accept-edits" ) ;
965+ const runtime = yield * adapter . openSession ( {
966+ threadId,
967+ providerSessionId : ProviderSessionId . make ( "provider-session-claude-accept-edits" ) ,
968+ modelSelection : CLAUDE_TEST_MODEL_SELECTION ,
969+ runtimePolicy,
970+ } ) ;
971+ const providerThread = yield * runtime . ensureThread ( {
972+ threadId,
973+ modelSelection : CLAUDE_TEST_MODEL_SELECTION ,
974+ runtimePolicy,
975+ } ) ;
976+ const now = yield * DateTime . now ;
977+ yield * runtime . startTurn (
978+ makeClaudeTestTurnInput ( {
979+ threadId,
980+ providerThread,
981+ now,
982+ attemptId : RunAttemptId . make ( "attempt-claude-accept-edits" ) ,
983+ text : "Run node." ,
984+ attachments : [ ] ,
985+ runtimePolicy,
986+ } ) ,
987+ ) ;
988+ assert . equal ( openedOptions ?. permissionMode , "acceptEdits" ) ;
989+ const canUseTool = openedOptions ?. canUseTool ;
990+ assert . isFunction ( canUseTool ) ;
991+
992+ const requestEvent = yield * runtime . events . pipe (
993+ Stream . filter ( ( event ) => event . type === "runtime_request.updated" ) ,
994+ Stream . runHead ,
995+ Effect . forkScoped ,
996+ ) ;
997+ const command = { command : "node -e 'console.log(42)'" } ;
998+ const decision = yield * Effect . promise ( ( ) =>
999+ canUseTool ! ( "Bash" , command , {
1000+ signal : new AbortController ( ) . signal ,
1001+ toolUseID : "tool-bash-accept-edits" ,
1002+ requestId : "request-bash-accept-edits" ,
1003+ } ) ,
1004+ ) . pipe ( Effect . forkScoped ) ;
1005+ // Without a callback that asks, the command is allowed before any
1006+ // request is raised.
1007+ const first = yield * Effect . raceFirst (
1008+ Fiber . join ( requestEvent ) . pipe (
1009+ Effect . map ( ( event ) => ( { type : "request" , event } ) as const ) ,
1010+ ) ,
1011+ Fiber . join ( decision ) . pipe (
1012+ Effect . map ( ( result ) => ( { type : "decision" , result } ) as const ) ,
1013+ ) ,
1014+ ) ;
1015+ assert . equal ( first . type , "request" , "the command ran without asking" ) ;
1016+ if ( first . type !== "request" ) return ;
1017+ const event = first . event ;
1018+ if ( Option . isNone ( event ) || event . value . type !== "runtime_request.updated" ) return ;
1019+ assert . equal ( event . value . runtimeRequest . kind , "command" ) ;
1020+
1021+ yield * runtime . respondToRuntimeRequest ( {
1022+ requestId : event . value . runtimeRequest . id ,
1023+ decision : "accept" ,
1024+ } ) ;
1025+ assert . equal ( ( yield * Fiber . join ( decision ) ) ?. behavior , "allow" ) ;
1026+ } ) . pipe ( Effect . provide ( Layer . merge ( idAllocatorLayer , NodeServices . layer ) ) ) ,
1027+ ) ,
1028+ ) ;
1029+ } ) ;
1030+
9231031describe ( "ClaudeAdapterV2 approval cancellation" , ( ) => {
9241032 it . effect ( "observes an approval signal that was already aborted" , ( ) =>
9251033 Effect . gen ( function * ( ) {
0 commit comments