@@ -109,6 +109,29 @@ interface Record extends PromptSnapshot {
109109 handle : PromptHandle ;
110110}
111111
112+ function bundledSkillBlockCount ( message : ContextMessage ) : number {
113+ return message . origin ?. kind === 'user' ? ( message . origin . skillActivations ?. length ?? 0 ) : 0 ;
114+ }
115+
116+ function stripBundledSkillBlocks ( message : ContextMessage ) : ContentPart [ ] {
117+ return message . content . slice ( bundledSkillBlockCount ( message ) ) ;
118+ }
119+
120+ function mergeSteerMessages ( records : readonly Record [ ] ) : ContextMessage {
121+ const skillActivations = records . flatMap ( ( item ) =>
122+ item . message . origin ?. kind === 'user' ? ( item . message . origin . skillActivations ?? [ ] ) : [ ] ,
123+ ) ;
124+ return {
125+ role : 'user' ,
126+ content : [
127+ ...records . flatMap ( ( item ) => item . message . content . slice ( 0 , bundledSkillBlockCount ( item . message ) ) ) ,
128+ ...records . flatMap ( ( item ) => stripBundledSkillBlocks ( item . message ) ) ,
129+ ] ,
130+ toolCalls : [ ] ,
131+ origin : skillActivations . length === 0 ? USER_PROMPT_ORIGIN : { kind : 'user' , skillActivations } ,
132+ } ;
133+ }
134+
112135export const promptLaunchingKey = defineState < boolean > ( 'prompt.launching' , ( ) => false ) ;
113136
114137export class AgentPromptService implements IAgentPromptService {
@@ -117,6 +140,7 @@ export class AgentPromptService implements IAgentPromptService {
117140 private readonly pending : Record [ ] = [ ] ;
118141 private readonly steered = new Map < string , Record [ ] > ( ) ;
119142 private readonly reservedPromptIds = new Set < string > ( ) ;
143+ private steering = 0 ;
120144 private fullCompactionService : IAgentFullCompactionService | undefined ;
121145 readonly hooks = { onBeforeSubmitPrompt : new OrderedHookSlot < PromptSubmitContext > ( ) } ;
122146
@@ -284,22 +308,41 @@ export class AgentPromptService implements IAgentPromptService {
284308 throw new Error2 ( ErrorCodes . PROMPT_NOT_FOUND , 'one or more prompts are not pending' ) ;
285309 }
286310 const selected = this . pending . filter ( ( item ) => ids . has ( item . id ) ) ;
287- for ( const item of selected ) this . pending . splice ( this . pending . indexOf ( item ) , 1 ) ;
288- const message : ContextMessage = {
289- role : 'user' , content : selected . flatMap ( ( item ) => item . message . content ) , toolCalls : [ ] , origin : USER_PROMPT_ORIGIN ,
290- } ;
291- const { message : rerouted , captions } = this . extractCompressionCaptions ( message ) ;
311+ const activeAtEntry = this . active ;
312+ const { message : rerouted , captions } = this . extractCompressionCaptions ( mergeSteerMessages ( selected ) ) ;
313+ await this . materializeDaemonRefs ( rerouted ) ;
314+ if ( selected . some ( ( item ) => ! this . pending . includes ( item ) ) || this . active !== activeAtEntry ) {
315+ throw new Error2 ( ErrorCodes . PROMPT_NOT_FOUND , 'one or more prompts are no longer pending' ) ;
316+ }
317+ this . steering ++ ;
318+ const removed : { readonly item : Record ; readonly index : number } [ ] = [ ] ;
319+ for ( const item of selected ) {
320+ const index = this . pending . indexOf ( item ) ;
321+ removed . push ( { item, index } ) ;
322+ this . pending . splice ( index , 1 ) ;
323+ }
292324 const request = new SteerStepRequest ( rerouted , captions , this . reminders , ( materialized ) => {
293325 void this . dispatcher . dispatch (
294326 new TurnSteer ( { input : materialized . content , origin : materialized . origin ?? USER_PROMPT_ORIGIN } ) ,
295327 ) ;
296328 } , ( ) => { } ) ;
297- const turn = ( await this . loop . enqueue ( request ) . assigned ) . turn ;
298- if ( turn === undefined ) throw new Error2 ( ErrorCodes . PROMPT_NOT_FOUND , 'no active turn to steer into' ) ;
329+ let turn : Turn | undefined ;
330+ try {
331+ turn = ( await this . loop . enqueue ( request ) . assigned ) . turn ;
332+ } catch {
333+ turn = undefined ;
334+ } finally {
335+ this . steering -- ;
336+ }
337+ if ( turn === undefined || this . active !== activeAtEntry ) {
338+ for ( const { item, index } of removed . reverse ( ) ) this . pending . splice ( index , 0 , item ) ;
339+ if ( this . active === undefined ) void this . startNext ( ) ;
340+ throw new Error2 ( ErrorCodes . PROMPT_NOT_FOUND , 'no active turn to steer into' ) ;
341+ }
299342 for ( const item of selected ) { item . state = 'steered' ; item . launchedDeferred . resolve ( turn ) ; }
300343 this . steered . set ( this . active . id , [ ...( this . steered . get ( this . active . id ) ?? [ ] ) , ...selected ] ) ;
301344 void this . dispatcher . dispatch (
302- new PromptSteered ( { activePromptId : this . active . id , promptIds : selected . map ( ( x ) => x . id ) , content : rerouted . content as ContentPart [ ] , steeredAt : new Date ( ) . toISOString ( ) } ) ,
345+ new PromptSteered ( { activePromptId : this . active . id , promptIds : selected . map ( ( x ) => x . id ) , content : selected . flatMap ( ( item ) => stripBundledSkillBlocks ( item . message ) ) , steeredAt : new Date ( ) . toISOString ( ) } ) ,
303346 ) ;
304347 return selected . map ( ( item ) => item . handle ) ;
305348 }
@@ -322,6 +365,7 @@ export class AgentPromptService implements IAgentPromptService {
322365
323366 async inject ( message : ContextMessage ) : Promise < Turn | undefined > {
324367 const { message : rerouted , captions } = this . extractCompressionCaptions ( message ) ;
368+ await this . materializeDaemonRefs ( rerouted ) ;
325369 const request = new SteerStepRequest ( rerouted , captions , this . reminders , ( materialized ) => {
326370 void this . dispatcher . dispatch (
327371 new TurnSteer ( { input : materialized . content , origin : materialized . origin ?? USER_PROMPT_ORIGIN } ) ,
@@ -339,17 +383,13 @@ export class AgentPromptService implements IAgentPromptService {
339383 }
340384
341385 private async startNext ( ) : Promise < void > {
342- if ( this . active !== undefined || this . launching ) return ;
386+ if ( this . active !== undefined || this . launching || this . steering > 0 ) return ;
343387 const item = this . pending . shift ( ) ; if ( item === undefined ) return ;
344388 this . launching = true ;
345389 try {
346390 if ( this . fullCompaction . compacting !== null && this . loop . status ( ) . state !== 'running' ) { this . pending . unshift ( item ) ; return ; }
347391 const { message, captions } = this . extractCompressionCaptions ( item . message ) ;
348- if ( message . content . some ( ( part ) => daemonFileRefFromPart ( part ) !== undefined ) ) {
349- const files = this . instantiation . invokeFunction ( ( accessor ) => accessor . get ( IFileService ) ) ;
350- const mediaStore = this . instantiation . invokeFunction ( ( accessor ) => accessor . get ( ISessionMediaStore ) ) ;
351- await materializePromptDaemonRefs ( message . content , { files, mediaStore } ) ;
352- }
392+ await this . materializeDaemonRefs ( message ) ;
353393 if ( await this . blockedByHook ( message , false ) ) {
354394 this . appendPrompt ( message , captions ) ; item . state = 'blocked' ; item . launchedDeferred . resolve ( undefined ) ;
355395 item . completionDeferred . resolve ( { promptId : item . id , result : undefined , state : 'blocked' } ) ;
@@ -381,6 +421,13 @@ export class AgentPromptService implements IAgentPromptService {
381421 void this . startNext ( ) ;
382422 }
383423
424+ private async materializeDaemonRefs ( message : ContextMessage ) : Promise < void > {
425+ if ( ! message . content . some ( ( part ) => daemonFileRefFromPart ( part ) !== undefined ) ) return ;
426+ const files = this . instantiation . invokeFunction ( ( accessor ) => accessor . get ( IFileService ) ) ;
427+ const mediaStore = this . instantiation . invokeFunction ( ( accessor ) => accessor . get ( ISessionMediaStore ) ) ;
428+ await materializePromptDaemonRefs ( message . content , { files, mediaStore } ) ;
429+ }
430+
384431 private async blockedByHook ( promptMessage : ContextMessage , isSteer : boolean ) : Promise < boolean > {
385432 const ctx = { promptMessage, isSteer, block : false } ; await this . hooks . onBeforeSubmitPrompt . run ( ctx ) ; return ctx . block ;
386433 }
@@ -420,7 +467,7 @@ export class AgentPromptService implements IAgentPromptService {
420467 private publishCompleted ( promptId : string , reason : 'completed' | 'failed' | 'blocked' ) : void { void this . dispatcher . dispatch ( new PromptCompleted ( { promptId, finishedAt : new Date ( ) . toISOString ( ) , reason } ) ) ; }
421468 private publishQueued ( record : Record ) : void {
422469 if ( ( record . message . origin ?? USER_PROMPT_ORIGIN ) . kind !== 'user' ) return ;
423- void this . dispatcher . dispatch ( new PromptQueued ( { promptId : record . id , content : record . message . content , queueLength : this . pending . length } ) ) ;
470+ void this . dispatcher . dispatch ( new PromptQueued ( { promptId : record . id , content : stripBundledSkillBlocks ( record . message ) , queueLength : this . pending . length } ) ) ;
424471 }
425472 private publishAborted ( promptId : string ) : void { void this . dispatcher . dispatch ( new PromptAborted ( { promptId, abortedAt : new Date ( ) . toISOString ( ) } ) ) ; }
426473}
0 commit comments