@@ -21,12 +21,16 @@ import { ContextService } from '../context/service.js';
2121import { MemoryService } from '../memory/index.js' ;
2222import { createLogger } from '@codingcode/infra/logger' ;
2323import { resolveSubagentEnabled , resolveAgentDisabled } from '../subagent/registry.js' ;
24- import { ProjectRuntimeService } from '../runtime/project-runtime.js' ;
24+ import { ProjectRuntimeService , modeToProfile } from '../runtime/project-runtime.js' ;
2525import { createDispatchAgentTool } from '../tools/domains/subagent/dispatch.js' ;
2626import { LLMFactoryService } from '../llm/factory.js' ;
2727import { getBuiltinTools } from '../tools/providers.js' ;
28+ import { submitPlanTool } from '../tools/domains/subagent/submit-plan.js' ;
2829import { canonicalizeSchema } from '../tools/utils/canonicalize-schema.js' ;
2930import { normalizePath } from '../core/path.js' ;
31+ import { isPlanProfile } from '../plan/index.js' ;
32+ import type { SessionMode } from '../session/types.js' ;
33+ import type { PermissionMode } from '../approval/types.js' ;
3034
3135const REACTIVE_COMPACT_MAX_RETRIES = 3 ;
3236import { RulesService } from '../rules/index.js' ;
@@ -116,9 +120,12 @@ export const sendMessage = (
116120 input : string ,
117121 cwd : string ,
118122 llm : LLMClient ,
119- options ? : {
123+ options : {
120124 signal ?: AbortSignal ;
121125 approvalOverride ?: any ;
126+ mode : SessionMode ;
127+ permissionMode : PermissionMode ;
128+ model : string ;
122129 }
123130) =>
124131 Effect . gen ( function * ( ) {
@@ -140,10 +147,30 @@ export const sendMessage = (
140147 yield * runtime . prepareProject ( normalizedCwd ) ;
141148 yield * skills . evictProject ( normalizedCwd ) ;
142149
143- const state = sessionId
144- ? yield * session . load ( normalizedCwd , sessionId )
145- : yield * session . create ( normalizedCwd , llm . modelInfo . model ) ;
146- state . model = llm . modelInfo . model ;
150+ if ( ! sessionId ) {
151+ const created = yield * session . create ( normalizedCwd , {
152+ model : options . model ,
153+ mode : options . mode ,
154+ permissionMode : options . permissionMode ,
155+ } ) ;
156+ const profile = modeToProfile ( options . mode ) ;
157+ yield * runtime . setSessionProfile (
158+ normalizedCwd ,
159+ created . sessionId ,
160+ profile ,
161+ options . permissionMode
162+ ) ;
163+ sessionId = created . sessionId ;
164+ }
165+ const state = yield * session . load ( normalizedCwd , sessionId ) ;
166+ if ( state . activeProfile ) {
167+ yield * runtime . restoreSessionProfile (
168+ normalizedCwd ,
169+ state . sessionId ,
170+ state . activeProfile ,
171+ state . permissionMode
172+ ) ;
173+ }
147174 state . memorySnapshot = memory . loadMemoryForPrompt ( state . cwd ) ;
148175 const sid = state . sessionId ;
149176
@@ -160,9 +187,7 @@ export const sendMessage = (
160187 }
161188 }
162189 const effectiveMaxSteps = profile ?. maxSteps ;
163- const effectiveApproval : any = profile ?. readonly
164- ? { permissionMode : 'bypass' }
165- : options ?. approvalOverride ;
190+ const effectiveApproval : any = options ?. approvalOverride ;
166191
167192 if ( profile ?. hooks ?. length ) {
168193 yield * hooks . attachSessionHooks ( sid , profile . hooks ) ;
@@ -187,6 +212,7 @@ export const sendMessage = (
187212 const stream = agent . runStream ( {
188213 state,
189214 llm : activeLlm ,
215+ profile,
190216 toolPolicy : policy ,
191217 maxStepsOverride : effectiveMaxSteps ,
192218 approvalOverride : effectiveApproval ,
@@ -221,6 +247,7 @@ export function agentLoop(
221247> {
222248 const state = opts . state ;
223249 const llm = opts . llm ;
250+ const profile = opts . profile ;
224251 const sessionId = state . sessionId ;
225252 const projectPath = state . cwd ;
226253
@@ -234,9 +261,12 @@ export function agentLoop(
234261 const { skillInstruction, systemPromptVariant, rulesText } = opts ;
235262
236263 const allAgentProfiles = runtime . listAgentProfiles ( projectPath ) ;
237- const agentProfiles = resolveSubagentEnabled ( projectPath )
264+ const enabledAgentProfiles = resolveSubagentEnabled ( projectPath )
238265 ? allAgentProfiles . filter ( ( p ) => ! resolveAgentDisabled ( projectPath , p . name ) )
239266 : [ ] ;
267+ const visibleAgentProfiles = isPlanProfile ( profile )
268+ ? enabledAgentProfiles . filter ( ( p ) => p . name === 'explore' )
269+ : enabledAgentProfiles ;
240270 const basePrompt =
241271 opts . systemOverride ??
242272 buildSystemPrompt ( {
@@ -245,8 +275,9 @@ export function agentLoop(
245275 shell : process . env . SHELL || process . env . ComSpec || 'bash' ,
246276 variant : systemPromptVariant ?? 'default' ,
247277 skillInstruction,
248- agentProfiles,
278+ agentProfiles : visibleAgentProfiles ,
249279 rules : rulesText ,
280+ profileSystemPrompt : profile ?. systemPrompt ,
250281 } ) ;
251282
252283 const memoryBlock = state . memorySnapshot ;
@@ -260,10 +291,11 @@ export function agentLoop(
260291 const effectiveMaxStopContinuations = opts . maxStopContinuations ?? maxStopContinuations ;
261292
262293 let messages : Message [ ] = [ ] ;
294+ let submittedPlanTitle : string | null = null ;
263295
264296 for ( let attempt = 0 ; attempt <= maxOverflowRetries ; attempt ++ ) {
265297 const payload = yield * Effect . sync ( ( ) =>
266- context . assemblePayload ( state . sessionId , state . projectPath , llm . modelInfo . maxTokens )
298+ context . assemblePayload ( state . transcriptPath , llm . modelInfo . maxTokens )
267299 ) ;
268300 messages = payload . messages ;
269301
@@ -281,6 +313,7 @@ export function agentLoop(
281313 let allToolDefs : ToolDefinition [ ] = [ ...builtinTools , ...( opts . mcpTools ?? [ ] ) ] ;
282314 if ( opts . dispatchTool && resolveSubagentEnabled ( projectPath ) )
283315 allToolDefs = [ ...allToolDefs , opts . dispatchTool ] ;
316+ if ( isPlanProfile ( profile ) ) allToolDefs = [ ...allToolDefs , submitPlanTool ] ;
284317
285318 const allowedByPolicy = opts . toolPolicy ?. allowedTools ;
286319 let filteredDefs = allToolDefs ;
@@ -303,8 +336,7 @@ export function agentLoop(
303336 const compressResult = yield * Effect . tryPromise ( {
304337 try : ( ) =>
305338 context . compactIfNeeded (
306- state . sessionId ,
307- state . projectPath ,
339+ state . transcriptPath ,
308340 messages ,
309341 llm . modelInfo . maxTokens ,
310342 llm
@@ -354,8 +386,7 @@ export function agentLoop(
354386 const compressResult = yield * Effect . tryPromise ( {
355387 try : ( ) =>
356388 context . compactWithLLM (
357- state . sessionId ,
358- state . projectPath ,
389+ state . transcriptPath ,
359390 llm . modelInfo . maxTokens ,
360391 llm ,
361392 undefined
@@ -438,6 +469,15 @@ export function agentLoop(
438469 continue ;
439470 }
440471
472+ if ( submittedPlanTitle !== null ) {
473+ yield * hooks . emit ( 'plan.ready' , {
474+ sessionId,
475+ projectPath,
476+ title : submittedPlanTitle ,
477+ } ) ;
478+ submittedPlanTitle = null ;
479+ }
480+
441481 yield * q . offer ( { _tag : 'Done' , content : resp . content } ) ;
442482 lastResult = Result . ok ( resp . content ) ;
443483 yield * hooks . emit ( 'agent.turn.end' , {
@@ -496,6 +536,14 @@ export function agentLoop(
496536 todoPrinted = true ;
497537 }
498538 }
539+
540+ const submitPlanCall = toolCalls ?. find ( ( tc ) => tc . name === 'submit_plan' ) ;
541+ const submitPlanResult = allResults . find (
542+ ( r ) => r . name === 'submit_plan' && r . type === 'ok'
543+ ) ;
544+ if ( submitPlanCall && submitPlanResult && submittedPlanTitle === null ) {
545+ submittedPlanTitle = String ( submitPlanCall . arguments ?. title ?? '' ) ;
546+ }
499547 }
500548
501549 if ( overflow ) continue ;
@@ -530,18 +578,19 @@ export function agentLoop(
530578 } ) . pipe (
531579 Effect . interruptible ,
532580 Effect . onInterrupt ( ( ) =>
533- Effect . sync ( ( ) => {
534- Effect . runSync (
535- q . offer ( { _tag : 'Error' , error : new AgentError ( 'AGENT_ABORTED' , 'cancelled' ) } )
536- ) ;
537- hooks
581+ Effect . gen ( function * ( ) {
582+ yield * Effect . sync ( ( ) => {
583+ Effect . runSync (
584+ q . offer ( { _tag : 'Error' , error : new AgentError ( 'AGENT_ABORTED' , 'cancelled' ) } )
585+ ) ;
586+ } ) ;
587+ yield * hooks
538588 . emit ( 'agent.turn.end' , {
539589 sessionId,
540590 turnId : state . currentTurnId ,
541591 status : 'aborted' ,
542592 } )
543- . pipe ( Effect . runPromise )
544- . catch ( ( ) => { } ) ;
593+ . pipe ( Effect . ignore ) ;
545594 } )
546595 ) ,
547596 Effect . ensuring (
0 commit comments