( frame: ResumeFrame, context: StreamingContext, execContext: ExecutionContext, options: CopilotLifecycleOptions, baseURL: string, workspaceId?: string )
| 398 | // null. Re-pause vs follow-on is disambiguated by checkpoint id: a re-pause keeps |
| 399 | // the same child id; the join continuation is a different (orchestrator) id. |
| 400 | async function driveOneChildChain( |
| 401 | frame: ResumeFrame, |
| 402 | context: StreamingContext, |
| 403 | execContext: ExecutionContext, |
| 404 | options: CopilotLifecycleOptions, |
| 405 | baseURL: string, |
| 406 | workspaceId?: string |
| 407 | ): Promise<AsyncContinuation | null> { |
| 408 | // ParentToolCallID is the SAME subagent's stable identity across re-pauses; |
| 409 | // the checkpoint id rotates each re-pause (the prior one is already claimed). |
| 410 | const parentToolCallId = frame.parentToolCallId |
| 411 | // Guarded (not cast): a per-subagent frame always carries its own checkpointId |
| 412 | // (isPerSubagentContinuation requires it), but a local guard keeps this driver |
| 413 | // correct on its own terms rather than trusting a caller-side invariant. |
| 414 | if (!frame.checkpointId) return null |
| 415 | let checkpointId = frame.checkpointId |
| 416 | let toolIds = frame.pendingToolIds |
| 417 | |
| 418 | for (;;) { |
| 419 | if (isAborted(options, context)) return null |
| 420 | |
| 421 | await waitForToolIds(context, toolIds) |
| 422 | const results = collectResultsForToolIds(context, toolIds, checkpointId) |
| 423 | |
| 424 | const leg = makeResumeLegContext(context) |
| 425 | await runResumeLegWithRetry( |
| 426 | `${baseURL}/api/tools/resume`, |
| 427 | { |
| 428 | streamId: context.messageId, |
| 429 | checkpointId, |
| 430 | userId: options.userId, |
| 431 | ...(workspaceId ? { workspaceId } : {}), |
| 432 | results, |
| 433 | }, |
| 434 | leg, |
| 435 | execContext, |
| 436 | options |
| 437 | ) |
| 438 | mergeResumeLegOutputs(context, leg) |
| 439 | |
| 440 | const cont = leg.awaitingAsyncContinuation |
| 441 | if (!cont) { |
| 442 | // The last finisher's leg, whose join continuation streamed the |
| 443 | // orchestrator to completion (done): nothing more to drive on this leg. |
| 444 | return null |
| 445 | } |
| 446 | // A NON-last finisher folds with a TERMINAL pause carrying the join id but |
| 447 | // NO pending tools and NO frames — the child's work is done and the join |
| 448 | // wakes on whichever sibling finishes last. End this leg cleanly; do NOT |
| 449 | // mistake the join id for an orchestrator follow-on and try to resume it. |
| 450 | const hasPending = (cont.pendingToolCallIds?.length ?? 0) > 0 |
| 451 | const hasFrames = (cont.frames?.length ?? 0) > 0 |
| 452 | if (!hasPending && !hasFrames) { |
| 453 | return null |
| 454 | } |
| 455 | // Re-pause is identified by THIS subagent's stable parentToolCallId (the |
| 456 | // checkpoint id rotates each re-pause). If present, keep driving this child |
| 457 | // with its new id + leaves. |
no test coverage detected