Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -841,11 +841,6 @@ export class ATXTransformHandler {
}
}

/**
* Normalize a beam-map repo item into the PascalCase shape the C# IDE consumes,
* tolerant of key-name variants from the web writer (repoName/repo/name,
* artifactId/beamArtifactId, stepId/planStepId, targetFramework/tfm).
*/
/**
* The wire contract with the web orchestrator / FES is not yet pinned to a single
* spelling: a plan-step / HITL id arrives as stepId, planStepId, or parentStepId
Expand All @@ -855,7 +850,9 @@ export class ATXTransformHandler {
*/
private getStepId(obj: any): string | undefined {
if (obj == null || typeof obj !== 'object') return undefined
return obj.stepId ?? obj.planStepId ?? obj.parentStepId ?? undefined
// Use || (not ??) so an empty-string id falls through to the next spelling; an empty
// stepId must not short-circuit coalescing and then fail every scope match.
return obj.stepId || obj.planStepId || obj.parentStepId || undefined
}

/**
Expand All @@ -879,6 +876,11 @@ export class ATXTransformHandler {
return { scopedLbv, allOutOfScopeLbv }
}

/**
* Normalize a beam-map repo item into the PascalCase shape the C# IDE consumes,
* tolerant of key-name variants from the web writer (repoName/repo/name,
* artifactId/beamArtifactId, stepId/planStepId, targetFramework/tfm).
*/
private normalizeBeamRepo(r: BeamMapRepo | null | undefined): Omit<BeamedRepoInfo, 'IsLbvOpen'> {
const o: BeamMapRepo = r || {}
return {
Expand Down Expand Up @@ -2001,13 +2003,46 @@ export class ATXTransformHandler {
// Plan not available yet
}

// Check for pending HITL tasks (e.g. missing packages) - job stays in PLANNING while HITL is pending
// Check for pending HITL tasks (e.g. missing packages) - job stays in PLANNING while HITL is pending.
// Beam multi-repo: several repos can have a pending LBV HITL at once; prefer the loaded repo's
// in-scope one (parity with AWAITING_HUMAN_INPUT). No-op when scope is empty, so non-beam is unchanged.
const hitls = await this.listHitls(request.WorkspaceId, request.TransformationJobId)
if (hitls && hitls.length > 0) {
const planScopeStepIds = (request.beamScopeStepIds || '')
.split(',')
.map(s => s.trim())
.filter(s => s.length > 0)
const { scopedLbv: scopedPlanLbv, allOutOfScopeLbv: planAllOutOfScopeLbv } =
this.selectScopedLbvHitl(hitls, planScopeStepIds)
// Beam multi-repo parity with EXECUTING / getHitlAgentArtifact: if scope is set and every
// pending HITL is a sibling repo's LBV (none in the loaded repo's subtree), do NOT surface a
// sibling's LBV — return plan-only. Prevents handing the IDE another repo's build HITL.
if (planScopeStepIds.length > 0 && planAllOutOfScopeLbv) {
this.logging.log(
'ATX: PLANNING job — all pending HITLs are sibling-repo LBV (none in loaded scope); not surfacing'
)
return {
TransformationJob: {
WorkspaceId: request.WorkspaceId,
JobId: request.TransformationJobId,
Status: jobStatus,
} as AtxTransformationJob,
TransformationPlan: plan,
} as AtxGetTransformInfoResponse
}
// When scope is set but no in-scope LBV matched, drop out-of-scope LBVs from the fallback
// pool so the plain find() below can't select a sibling's LBV (mixed pending-set case).
const planFallbackPool =
planScopeStepIds.length > 0 && !scopedPlanLbv
? hitls.filter(h => h.tag !== 'local-build-verification')
: hitls
const hitl =
hitls.find(h => h.tag === 'local-build-verification') ||
hitls.find(h => h.tag === 'missing-packages' || h.tag === 'handle_missing_packages_hitl') ||
hitls[0]
scopedPlanLbv ||
planFallbackPool.find(h => h.tag === 'local-build-verification') ||
planFallbackPool.find(
h => h.tag === 'missing-packages' || h.tag === 'handle_missing_packages_hitl'
) ||
planFallbackPool[0]
this.logging.log(`ATX: Found HITL task - tag: ${hitl.tag}, hasArtifact: ${!!hitl.agentArtifact}`)
// For missing packages HITL, try to download artifact if available
if (hitl.tag === 'handle_missing_packages_hitl' || hitl.tag === 'missing-packages') {
Expand Down Expand Up @@ -2036,8 +2071,11 @@ export class ATXTransformHandler {
}
if (hitl.tag === 'local-build-verification') {
this.jobsPastLocalBuild.add(request.TransformationJobId)
// Forward the HITL's plan-step id so a multi-repo beam IDE can confirm this LBV
// belongs to the loaded repo (parity with AWAITING_HUMAN_INPUT). Undefined when absent.
const planLbvStepId = this.getStepId(hitl)
this.logging.log(
`ATX: ${jobStatus} job has pending LBV HITL — taskId=${hitl.taskId}; surfacing AWAITING_HUMAN_INPUT to IDE`
`ATX: ${jobStatus} job has pending LBV HITL — taskId=${hitl.taskId} stepId=${planLbvStepId ?? '<none>'}; surfacing AWAITING_HUMAN_INPUT to IDE`
)
return {
TransformationJob: {
Expand All @@ -2047,6 +2085,7 @@ export class ATXTransformHandler {
} as AtxTransformationJob,
HitlTag: hitl.tag,
HitlTaskId: hitl.taskId,
StepInformation: planLbvStepId ? { StepId: planLbvStepId } : undefined,
TransformationPlan: plan,
} as AtxGetTransformInfoResponse
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,30 @@ describe('ATXTransformHandler - getTransformInfo', () => {
expect((handler as any).jobsPastLocalBuild.has('job-123')).to.be.true
})

it('should NOT surface a sibling repo LBV when status is PLANNING and all pending LBVs are out of the loaded scope', async () => {
// Beam multi-repo: the IDE loaded one repo (beamScopeStepIds = its subtree). Every pending
// HITL is a local-build-verification for a DIFFERENT (sibling) repo's plan step. Surfacing
// one would make the IDE build the loaded solution against a sibling's HITL → false-green.
// Mirrors the EXECUTING/getHitlAgentArtifact guard: return the plan-only view instead.
getJobStub.resolves({ statusDetails: { status: 'PLANNING' } })
getTransformationPlanStub.resolves({ Root: { Children: [] } })
listHitlsStub.resolves([
{ tag: 'local-build-verification', taskId: 'task-sib1', stepId: 'sibling-step-1' },
{ tag: 'local-build-verification', taskId: 'task-sib2', stepId: 'sibling-step-2' },
])

const result = await handler.getTransformInfo({
...baseRequest,
beamScopeStepIds: 'loaded-step-a,loaded-step-b',
})

// Plan-only view: original job status preserved, no HITL surfaced to the IDE.
expect(result?.TransformationJob.Status).to.equal('PLANNING')
expect(result?.HitlTag).to.be.undefined
expect(result?.HitlTaskId).to.be.undefined
expect(result?.TransformationPlan).to.deep.equal({ Root: { Children: [] } })
})

it('should filter pre-job mode-selection -checkpoint HITL before LBV has run', async () => {
getJobStub.resolves({ statusDetails: { status: 'PLANNING' } })
getTransformationPlanStub.resolves({ Root: { Children: [] } })
Expand Down Expand Up @@ -4167,6 +4191,15 @@ describe('ATXTransformHandler - Beam to IDE', () => {
expect(g('str')).to.be.undefined
expect(g({ other: 'x' })).to.be.undefined
})

it('skips an empty-string stepId and falls through to planStepId / parentStepId (|| not ??)', () => {
const g = (o: any) => (handler as any).getStepId(o)
// Empty stepId must NOT short-circuit coalescing (the ??→|| hardening) — an empty
// string is falsy under ||, so the non-empty planStepId is returned.
expect(g({ stepId: '', planStepId: 'step-1' })).to.equal('step-1')
// Empty stepId + empty planStepId → fall all the way through to parentStepId.
expect(g({ stepId: '', planStepId: '', parentStepId: 'parent-1' })).to.equal('parent-1')
})
})

// --- normalizeBeamRepo: tolerant wire-shape mapping ---
Expand Down
Loading