@@ -52,6 +52,7 @@ function buildLifecycleBootstrapStateUtils(ctx) {
5252 getStatePaths,
5353 getActiveScope,
5454 getActiveNamespaceRoot,
55+ listWorkspaceProjects,
5556 getBootstrapAgent,
5657 getWorkspaceNamespaceRoot,
5758 readProfileMode,
@@ -733,7 +734,12 @@ function buildLifecycleBootstrapStateUtils(ctx) {
733734 return { allowed : false , suspicious : true , reason : 'context tool requires an exact server/tool identity' }
734735 }
735736 if ( identity . canonical ) {
736- if ( ! targetMatches ( args , state ) ) {
737+ const requestedProject = String ( args . project || '' ) . trim ( )
738+ const pendingPlanTarget = identity . tool === 'profile_context_plan' &&
739+ ! acquisition . targetResolved &&
740+ requestedProject &&
741+ listWorkspaceProjects ( ) . includes ( requestedProject )
742+ if ( ! targetMatches ( args , state ) && ! pendingPlanTarget ) {
737743 return { allowed : false , suspicious : true , reason : 'context tool target does not match the active project' }
738744 }
739745 if ( identity . tool === 'skill_route' ) {
@@ -766,7 +772,9 @@ function buildLifecycleBootstrapStateUtils(ctx) {
766772 if ( identity . tool === 'profile_context_plan' ) {
767773 const epoch = String ( args . contextEpoch || '' ) . trim ( )
768774 const planKey = stableDigest ( { contextEpoch : acquisition . contextEpoch , canonical : identity . canonical , args } )
769- if ( ! acquisition . targetResolved || ! epoch || epoch !== acquisition . contextEpoch ) {
775+ if ( ( ! acquisition . targetResolved && ! pendingPlanTarget ) ||
776+ ! epoch ||
777+ epoch !== acquisition . contextEpoch ) {
770778 return { allowed : false , suspicious : true , reason : 'profile plan must use the current bound contextEpoch' }
771779 }
772780 if ( acquisition . failedPlanKeys . includes ( planKey ) ) {
@@ -782,6 +790,10 @@ function buildLifecycleBootstrapStateUtils(ctx) {
782790 args,
783791 argsDigest : stableDigest ( args ) ,
784792 planKey,
793+ targetProject : pendingPlanTarget ? requestedProject : acquisition . project ,
794+ targetActiveRoot : pendingPlanTarget
795+ ? normalizePath ( getActiveNamespaceRoot ( state , requestedProject , 'project' ) )
796+ : acquisition . activeRoot ,
785797 sourceIds : [ ]
786798 }
787799 }
@@ -936,8 +948,8 @@ function buildLifecycleBootstrapStateUtils(ctx) {
936948 attemptId : `pre-${ crypto . randomUUID ( ) } ` ,
937949 toolCallId : getToolCallId ( payload ) ,
938950 contextEpoch : acquisition . contextEpoch ,
939- activeRoot : acquisition . activeRoot ,
940- project : acquisition . project ,
951+ activeRoot : classified . targetActiveRoot || acquisition . activeRoot ,
952+ project : classified . targetProject || acquisition . project ,
941953 canonical : classified . canonical ,
942954 server : classified . server ,
943955 tool : classified . tool ,
@@ -1301,8 +1313,8 @@ function buildLifecycleBootstrapStateUtils(ctx) {
13011313 : null
13021314 if ( ! plan && extracted . outcome ?. transportSuccess && typeof extracted . outcome . payload === 'string' && ! inlineBody ) {
13031315 const recovered = readContextPlanObservation ( {
1304- activeRoot : acquisition . activeRoot ,
1305- project : acquisition . project ,
1316+ activeRoot : attempt . activeRoot ,
1317+ project : attempt . project ,
13061318 contextEpoch : acquisition . contextEpoch ,
13071319 notBefore : attempt . startedAt
13081320 } )
@@ -1325,8 +1337,8 @@ function buildLifecycleBootstrapStateUtils(ctx) {
13251337 }
13261338 }
13271339 const identityMatches = ! ! plan && plan . identity . contextEpoch === acquisition . contextEpoch &&
1328- normalizePath ( plan . identity . activeRoot ) === acquisition . activeRoot &&
1329- plan . identity . project === acquisition . project &&
1340+ normalizePath ( plan . identity . activeRoot ) === normalizePath ( attempt . activeRoot ) &&
1341+ plan . identity . project === attempt . project &&
13301342 String ( attempt . args . contextEpoch || '' ) === acquisition . contextEpoch
13311343 if ( ! identityMatches ) {
13321344 if ( ! acquisition . failedPlanKeys . includes ( attempt . planKey ) ) acquisition . failedPlanKeys . push ( attempt . planKey )
@@ -1390,6 +1402,9 @@ function buildLifecycleBootstrapStateUtils(ctx) {
13901402 }
13911403 acquisition . plan = plan
13921404 acquisition . receipt = receipt
1405+ acquisition . activeRoot = normalizePath ( plan . identity . activeRoot )
1406+ acquisition . project = plan . identity . project
1407+ acquisition . targetResolved = true
13931408 acquisition . stageTiming = {
13941409 ...plan . stageTiming ,
13951410 hostDeliveredBytes : extracted . outcome ?. telemetry ?. bytes ?? null
@@ -1404,6 +1419,7 @@ function buildLifecycleBootstrapStateUtils(ctx) {
14041419 /** Correlate an observable PostToolUse result and advance only independently proven sources. */
14051420 function recordContextPostToolUse ( state , payload ) {
14061421 const acquisition = syncContextProjection ( state )
1422+ const targetWasResolved = acquisition . targetResolved === true
14071423 const identity = canonicalContextTool ( payload )
14081424 if ( ! identity . canonical && ! isBootstrapReadTool ( payload , state ) ) {
14091425 return { observed : false , ignored : true }
@@ -1440,7 +1456,20 @@ function buildLifecycleBootstrapStateUtils(ctx) {
14401456 acquisition . lastError = contextError ( 'CONTEXT_PLAN_INVALID' , 'PostToolUse input does not match the correlated PreToolUse attempt.' )
14411457 }
14421458 } else if ( attempt . kind === 'plan' ) {
1443- installObservedPlan ( acquisition , attempt , payload )
1459+ const installed = installObservedPlan ( acquisition , attempt , payload )
1460+ if ( installed && ! targetWasResolved ) {
1461+ state . activeProject = acquisition . project
1462+ state . activeScope = 'project'
1463+ state . activeProjectSource = 'context-plan'
1464+ state . mode = readProfileMode ( state , acquisition . project )
1465+ state . stickyProject = {
1466+ project : acquisition . project ,
1467+ source : 'context-plan' ,
1468+ sessionKey : acquisition . hostSessionId ,
1469+ updatedAt : new Date ( ) . toISOString ( ) ,
1470+ updatedAtMs : Date . now ( )
1471+ }
1472+ }
14441473 } else if ( attempt . kind === 'profile-load' && acquisition . plan && acquisition . receipt ) {
14451474 const parsed = profileSourceResults ( acquisition , attempt , outcome )
14461475 if ( parsed . drift ) {
@@ -1468,7 +1497,14 @@ function buildLifecycleBootstrapStateUtils(ctx) {
14681497 } ] . slice ( - 40 )
14691498 state . contextAcquisition = acquisition
14701499 syncContextProjection ( state )
1471- return { observed : true , attempt, outcome }
1500+ return {
1501+ observed : true ,
1502+ attempt,
1503+ outcome,
1504+ targetRebound : ! targetWasResolved && acquisition . targetResolved === true ,
1505+ project : acquisition . project ,
1506+ activeRoot : acquisition . activeRoot
1507+ }
14721508 }
14731509
14741510 function updateBootstrapState ( state , payload ) {
0 commit comments