pi-subagents 0.57.0 → 0.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/docs/agents.md +18 -9
  3. package/docs/configuration.md +4 -4
  4. package/docs/extension-api.md +40 -1
  5. package/docs/models.md +27 -4
  6. package/docs/observability.md +1 -1
  7. package/docs/tool-reference.md +92 -6
  8. package/docs/workflows.md +115 -0
  9. package/package.json +3 -1
  10. package/prompts/review-loop.md +2 -2
  11. package/skills/council-mode/SKILL.md +1 -1
  12. package/skills/pi-subagents/SKILL.md +3 -1
  13. package/skills/pi-subagents/references/execution-controls.md +6 -2
  14. package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
  15. package/skills/pi-subagents/references/prompting-and-roles.md +26 -5
  16. package/src/agents/agent-management.ts +28 -17
  17. package/src/agents/agent-serializer.ts +9 -2
  18. package/src/agents/agents.ts +103 -27
  19. package/src/agents/runtime-agent-events.ts +70 -0
  20. package/src/agents/runtime-agent-registry.ts +19 -18
  21. package/src/api/agents.ts +10 -5
  22. package/src/api/background-work.ts +5 -1
  23. package/src/api/delegation.ts +0 -7
  24. package/src/api/preflight.ts +32 -10
  25. package/src/extension/fanout-child.ts +5 -3
  26. package/src/extension/index.ts +66 -27
  27. package/src/extension/public-execution.ts +15 -2
  28. package/src/extension/rpc.ts +35 -14
  29. package/src/extension/schemas.ts +37 -12
  30. package/src/extension/tool-description.ts +25 -6
  31. package/src/integrations/herdr-status.ts +5 -0
  32. package/src/intercom/result-intercom.ts +2 -0
  33. package/src/profiles/profiles.ts +5 -6
  34. package/src/runs/background/active-async-capacity.ts +2 -2
  35. package/src/runs/background/async-execution.ts +78 -46
  36. package/src/runs/background/async-job-tracker.ts +14 -12
  37. package/src/runs/background/async-resume.ts +29 -27
  38. package/src/runs/background/async-status-snapshot.ts +23 -261
  39. package/src/runs/background/async-status.ts +68 -7
  40. package/src/runs/background/chain-append.ts +8 -3
  41. package/src/runs/background/chain-root-attachment.ts +60 -8
  42. package/src/runs/background/fleet-view.ts +21 -11
  43. package/src/runs/background/notify.ts +184 -9
  44. package/src/runs/background/result-delivery-ownership.ts +45 -0
  45. package/src/runs/background/result-files.ts +2 -1
  46. package/src/runs/background/result-watcher.ts +29 -10
  47. package/src/runs/background/resume-guidance.ts +1 -1
  48. package/src/runs/background/retained-children.ts +1 -1
  49. package/src/runs/background/run-status.ts +18 -8
  50. package/src/runs/background/scheduled-runs.ts +86 -7
  51. package/src/runs/background/stale-run-reconciler.ts +10 -4
  52. package/src/runs/background/steering.ts +4 -14
  53. package/src/runs/background/subagent-runner.ts +389 -350
  54. package/src/runs/background/subagent-wait.ts +58 -10
  55. package/src/runs/background/terminal-run-index.ts +1 -1
  56. package/src/runs/background/wait-completions.ts +22 -1
  57. package/src/runs/background/wait-config.ts +23 -9
  58. package/src/runs/background/wait-tool.ts +9 -2
  59. package/src/runs/foreground/async-steering-action.ts +2 -2
  60. package/src/runs/foreground/execution.ts +134 -121
  61. package/src/runs/foreground/foreground-control.ts +3 -0
  62. package/src/runs/foreground/foreground-history.ts +1 -0
  63. package/src/runs/foreground/subagent-executor.ts +527 -257
  64. package/src/runs/foreground/workflow-detach-reconcile.ts +130 -120
  65. package/src/runs/shared/abort-recovery.ts +119 -0
  66. package/src/runs/shared/async-status-projection.ts +463 -0
  67. package/src/runs/shared/child-identity.ts +19 -4
  68. package/src/runs/shared/child-launch-plan.ts +151 -0
  69. package/src/runs/shared/completion-evidence.ts +89 -0
  70. package/src/runs/shared/completion-guard.ts +5 -4
  71. package/src/runs/shared/dynamic-fanout.ts +3 -3
  72. package/src/runs/shared/fast-mode-extension.ts +5 -5
  73. package/src/runs/shared/host-step-status.ts +230 -0
  74. package/src/runs/shared/lane-metadata.ts +105 -0
  75. package/src/runs/shared/launch-cwd.ts +16 -0
  76. package/src/runs/shared/long-running-guard.ts +2 -1
  77. package/src/runs/shared/mcp-config-sources.ts +422 -0
  78. package/src/runs/shared/mcp-direct-tool-allowlist.ts +221 -158
  79. package/src/runs/shared/mcp-direct-tool-grant.ts +197 -0
  80. package/src/runs/shared/model-exclusions.ts +12 -0
  81. package/src/runs/shared/model-fallback.ts +25 -6
  82. package/src/runs/shared/nested-events.ts +6 -2
  83. package/src/runs/shared/nested-render.ts +7 -3
  84. package/src/runs/shared/parallel-handoff.ts +419 -7
  85. package/src/runs/shared/parallel-utils.ts +15 -1
  86. package/src/runs/shared/pi-args.ts +81 -8
  87. package/src/runs/shared/single-output.ts +44 -4
  88. package/src/runs/shared/subagent-prompt-runtime.ts +98 -13
  89. package/src/runs/shared/worktree-cleanup-plan.ts +847 -0
  90. package/src/runs/shared/worktree.ts +18 -0
  91. package/src/shared/child-session-name.ts +46 -0
  92. package/src/shared/extension-context.ts +24 -0
  93. package/src/shared/formatters.ts +11 -2
  94. package/src/shared/launch-contract.ts +5 -1
  95. package/src/shared/settings.ts +9 -103
  96. package/src/shared/types.ts +240 -30
  97. package/src/shared/utils.ts +41 -84
  98. package/src/slash/delegation-adapters.ts +1 -8
  99. package/src/slash/delegation-request.ts +0 -4
  100. package/src/slash/slash-bridge.ts +1 -2
  101. package/src/slash/slash-commands.ts +369 -90
  102. package/src/slash/slash-live-state.ts +22 -11
  103. package/src/slash/subagents-admin.ts +3 -0
  104. package/src/tui/fleet-status.ts +125 -74
  105. package/src/tui/fleet.ts +11 -5
  106. package/src/tui/render.ts +330 -33
  107. package/src/watchdog/turn-delta.ts +1 -1
  108. package/src/workflows/chat-progress.ts +6 -3
  109. package/src/workflows/host-command.ts +230 -0
  110. package/src/workflows/scripted-workflow.ts +496 -35
  111. package/src/workflows/workflow-child-summary.ts +9 -5
  112. package/src/workflows/workflow-preflight.ts +270 -0
  113. package/src/workflows/workflow-receipt.ts +79 -5
  114. package/src/workflows/workflow-settlement.ts +246 -0
  115. package/src/runs/shared/turn-budget.ts +0 -98
@@ -5,7 +5,6 @@
5
5
  import { spawn } from "node:child_process";
6
6
  import { randomUUID } from "node:crypto";
7
7
  import * as fs from "node:fs";
8
- import * as os from "node:os";
9
8
  import * as path from "node:path";
10
9
  import { fileURLToPath } from "node:url";
11
10
  import { createRequire } from "node:module";
@@ -14,20 +13,22 @@ import { discoverAgents, formatUnknownAgentError, unknownAgentDiagnosticContext,
14
13
  import { appendAgentRefinementOverlay } from "../../agents/agent-refinements.ts";
15
14
  import { writePrivateAtomicJson } from "../../shared/atomic-json.ts";
16
15
  import { currentCompletionOwnerId } from "../../shared/completion-owner.ts";
16
+ import { planChildLaunch, resolveStepBehavior, suppressProgressForReadOnlyTask, type ResolvedStepBehavior } from "../shared/child-launch-plan.ts";
17
17
  import { applyThinkingSuffix, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan } from "../shared/pi-args.ts";
18
18
  import { injectOutputPathSystemPrompt, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
19
- import { buildChainInstructions, isDynamicParallelStep, isParallelStep, resolveChainPath, resolveExistingReadPaths, resolveStepBehavior, suppressProgressForReadOnlyTask, writeInitialProgressFile, type ChainStep, type ResolvedStepBehavior, type SequentialStep, type StepOverrides } from "../../shared/settings.ts";
19
+ import { buildChainInstructions, isDynamicParallelStep, isParallelStep, resolveExistingReadPaths, writeInitialProgressFile, type ChainStep, type SequentialStep, type StepOverrides } from "../../shared/settings.ts";
20
20
  import type { RunnerStep } from "../shared/parallel-utils.ts";
21
21
  import type { ContextMode } from "../shared/context-mode.ts";
22
22
  import { resolvePiPackageRoot } from "../shared/pi-spawn.ts";
23
+ import { preflightLaunchCwd } from "../shared/launch-cwd.ts";
23
24
  import { resolveNodeExecutable } from "../../shared/node-executable.ts";
24
25
  import { buildSkillInjection, normalizeSkillInput, resolveSkillsWithFallback } from "../../agents/skills.ts";
25
26
  import { buildAgentMemoryInjection } from "../../agents/agent-memory.ts";
26
27
  import { PI_CODING_AGENT_PACKAGE_ROOT_ENV, PROMPT_REDACTED, resolveChildCwd } from "../../shared/utils.ts";
27
- import { buildModelCandidates, inheritsParentModel, resolveEffectiveSubagentModel, resolveModelCandidate, resolveSubagentModelOverride, type AvailableModelInfo, type ParentModel } from "../shared/model-fallback.ts";
28
+ import { buildModelCandidates, inheritsParentModel, resolveEffectiveSubagentModel, resolveSubagentModelOverride, type AvailableModelInfo, type ParentModel } from "../shared/model-fallback.ts";
28
29
  import { resolveToolTimeoutMs, toolTimeoutFromEnv } from "../shared/tool-timeout.ts";
29
30
  import { resolveModelScopesForAgent, type ModelScopeConfig } from "../shared/model-scope.ts";
30
- import { resolveEffectiveThinking } from "../../shared/model-info.ts";
31
+ import { findModelInfo, resolveEffectiveThinking } from "../../shared/model-info.ts";
31
32
  import { assertThinkingWithinCeiling, decodeThinkingCeiling, intersectThinkingCeilings, SUBAGENT_THINKING_CEILING_ENV, type ThinkingLevel } from "../../shared/thinking-ceiling.ts";
32
33
  import { resolveExpectedWorktreeAgentCwd } from "../shared/worktree.ts";
33
34
  import { buildWorkflowGraphSnapshot } from "../shared/workflow-graph.ts";
@@ -48,12 +49,12 @@ import {
48
49
  type MaxOutputConfig,
49
50
  type NestedRouteInfo,
50
51
  type ResolvedControlConfig,
51
- type ResolvedTurnBudget,
52
52
  type ResolvedToolBudget,
53
53
  type RunFanoutBudgetDescriptor,
54
54
  type ToolBudgetConfig,
55
55
  type SubagentRunMode,
56
56
  type SteeringRecoveryDescriptor,
57
+ type WorkflowLaneMetadata,
57
58
  type UsageBudgetConfig,
58
59
  DIRS,
59
60
  SUBAGENT_ASYNC_STARTED_EVENT,
@@ -64,7 +65,6 @@ import {
64
65
  } from "../../shared/types.ts";
65
66
  import { nestedResultsPath, nestedSummaryFromAsyncStatus, resolveInheritedNestedRouteFromEnv, resolveNestedParentAddressFromEnv, writeNestedEvent } from "../shared/nested-events.ts";
66
67
  import { resultFilePath } from "./result-files.ts";
67
- import { appendTurnBudgetSystemPrompt, initialTurnBudgetState } from "../shared/turn-budget.ts";
68
68
  import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
69
69
  import { usageBudgetState } from "../shared/usage-budget.ts";
70
70
  import type { ImportedAsyncRoot } from "./chain-root-attachment.ts";
@@ -77,6 +77,7 @@ import { assertAgentAllowedByCapabilityCeiling, decodeSubagentCapabilityCeiling,
77
77
  import { agentDefinitionDigest, launchBindingDigest } from "../../shared/launch-contract.ts";
78
78
  import { resolvePermissionRules, type PermissionConfig } from "../shared/permissions.ts";
79
79
  import { normalizeExtensionBindings, omitExtensionBindingsEnv, type ExtensionBindings } from "../shared/extension-bindings.ts";
80
+ import { assertWorkflowLaneKey, normalizeWorkflowLaneMetadata } from "../shared/lane-metadata.ts";
80
81
 
81
82
  const require = createRequire(import.meta.url);
82
83
  const piPackageRoot = resolvePiPackageRoot();
@@ -172,6 +173,7 @@ interface AsyncChainParams {
172
173
  dynamicFanoutMaxItems?: number;
173
174
  maxSubagentDepth: number;
174
175
  waitToolEnabled?: boolean;
176
+ waitToolDefaultTimeoutMs?: number;
175
177
  worktreeSetupHook?: string;
176
178
  worktreeSetupHookTimeoutMs?: number;
177
179
  worktreeBaseDir?: string;
@@ -182,7 +184,6 @@ interface AsyncChainParams {
182
184
  acceptance?: AcceptanceInput;
183
185
  fast?: boolean;
184
186
  timeoutMs?: number;
185
- turnBudget?: ResolvedTurnBudget;
186
187
  toolBudget?: ResolvedToolBudget;
187
188
  usageBudget?: UsageBudgetConfig;
188
189
  configToolBudget?: ResolvedToolBudget;
@@ -199,6 +200,7 @@ interface AsyncChainParams {
199
200
  runFanoutBudget?: RunFanoutBudgetDescriptor;
200
201
  parentWorkflowRunId?: string;
201
202
  workflowKey?: string;
203
+ lane?: WorkflowLaneMetadata;
202
204
  activeAsyncCapacity?: ActiveAsyncCapacityHandle;
203
205
  }
204
206
 
@@ -212,6 +214,7 @@ interface AsyncSingleParams {
212
214
  recoveryAgentConfig?: AgentConfig;
213
215
  ctx: AsyncExecutionContext;
214
216
  cwd?: string;
217
+ requestedCwd?: string;
215
218
  maxOutput?: MaxOutputConfig;
216
219
  artifactsDir?: string;
217
220
  artifactConfig: ArtifactConfig;
@@ -226,6 +229,7 @@ interface AsyncSingleParams {
226
229
  reads?: string[] | false;
227
230
  outputMode?: "inline" | "file-only";
228
231
  outputBaseDir?: string;
232
+ outputClaimPath?: string;
229
233
  agentContract?: AgentContract;
230
234
  structuredOutputSchema?: JsonSchemaObject;
231
235
  modelOverride?: string;
@@ -235,6 +239,7 @@ interface AsyncSingleParams {
235
239
  availableModels?: AvailableModelInfo[];
236
240
  maxSubagentDepth: number;
237
241
  waitToolEnabled?: boolean;
242
+ waitToolDefaultTimeoutMs?: number;
238
243
  worktreeSetupHook?: string;
239
244
  worktreeSetupHookTimeoutMs?: number;
240
245
  worktreeBaseDir?: string;
@@ -249,7 +254,6 @@ interface AsyncSingleParams {
249
254
  absoluteDeadlineAt?: number;
250
255
  /** Optional per-call hard toolTimeoutMs override (highest precedence). */
251
256
  toolTimeoutMs?: number;
252
- turnBudget?: { maxTurns: number; graceTurns?: number };
253
257
  toolBudget?: ResolvedToolBudget | ToolBudgetConfig;
254
258
  usageBudget?: UsageBudgetConfig;
255
259
  configToolBudget?: ResolvedToolBudget;
@@ -263,6 +267,7 @@ interface AsyncSingleParams {
263
267
  runFanoutBudget?: RunFanoutBudgetDescriptor;
264
268
  parentWorkflowRunId?: string;
265
269
  workflowKey?: string;
270
+ lane?: WorkflowLaneMetadata;
266
271
  workflowAwaitAsync?: boolean;
267
272
  activeAsyncCapacity?: ActiveAsyncCapacityHandle;
268
273
  externalJobFollowUp?: {
@@ -301,6 +306,7 @@ export interface AsyncRunnerStepBuildParams {
301
306
  dynamicFanoutMaxItems?: number;
302
307
  maxSubagentDepth: number;
303
308
  waitToolEnabled?: boolean;
309
+ waitToolDefaultTimeoutMs?: number;
304
310
  worktreeBaseDir?: string;
305
311
  asyncDir: string;
306
312
  outputBaseDir?: string;
@@ -512,20 +518,14 @@ export function emitProcessTerminalEvent(ctx: AsyncExecutionContext, proof: unkn
512
518
  }
513
519
  }
514
520
 
515
- function spawnRunner(cfg: object, suffix: string, cwd: string, initialStatus: Omit<AsyncStatus, "pid" | "processTerminal">, initialStatusPath: string, onProcessTerminal?: (proof: unknown) => void): SpawnRunnerResult {
521
+ function spawnRunner(cfg: object, suffix: string, cwd: string, initialStatus: Omit<AsyncStatus, "pid" | "processTerminal">, initialStatusPath: string, onProcessTerminal?: (proof: unknown) => void, requestedCwd = cwd): SpawnRunnerResult {
522
+ const cwdError = preflightLaunchCwd(requestedCwd, cwd);
523
+ if (cwdError) return { error: cwdError };
524
+
516
525
  if (!jitiCliPath) {
517
526
  return { error: "upstream jiti for TypeScript execution could not be found; ensure package dependencies are installed" };
518
527
  }
519
528
 
520
- try {
521
- const cwdStats = fs.statSync(cwd);
522
- if (!cwdStats.isDirectory()) {
523
- return { error: `cwd is not a directory: ${cwd}` };
524
- }
525
- } catch {
526
- return { error: `cwd does not exist: ${cwd}` };
527
- }
528
-
529
529
  fs.mkdirSync(TEMP_ROOT_DIR, { recursive: true });
530
530
  const cfgPath = getAsyncConfigPath(suffix);
531
531
  const runnerProcessInstanceId = randomUUID();
@@ -808,19 +808,21 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
808
808
  envValue: params.toolTimeoutMsEnv ?? toolTimeoutFromEnv(),
809
809
  });
810
810
  if (resolvedToolTimeout.error) throw new AsyncStartValidationError(resolvedToolTimeout.error);
811
- const stepCwd = resolveChildCwd(runnerCwd, s.cwd);
812
- const instructionCwd = behaviorCwd ?? stepCwd;
813
- const readExistenceCwd = behaviorCwd ? stepCwd : instructionCwd;
814
- let behavior = suppressProgressForReadOnlyTask(resolvedBehavior ?? resolveStepBehavior(a, buildStepOverrides(s), chainSkills), s.task, originalTask);
815
- const inheritedRelativeParallelOutput = parallelOutputNamespace && s.output === undefined && typeof behavior.output === "string" && !path.isAbsolute(behavior.output);
816
- if (inheritedRelativeParallelOutput && parallelOutputNamespace.taskIndex !== undefined) {
817
- behavior = {
818
- ...behavior,
819
- output: path.join(`parallel-${parallelOutputNamespace.stepIndex}`, `${parallelOutputNamespace.taskIndex}-${s.agent}`, behavior.output as string),
820
- };
821
- }
822
- const namespaceOutputPath = Boolean(inheritedRelativeParallelOutput && parallelOutputNamespace.taskIndex === undefined);
823
- const skillNames = behavior.skills === false ? [] : behavior.skills;
811
+ const launchPlan = planChildLaunch({
812
+ agentConfig: a,
813
+ stepOverrides: buildStepOverrides(s),
814
+ task: s.task,
815
+ originalTask,
816
+ runnerCwd,
817
+ runtimeCwd: ctx.cwd,
818
+ stepCwdInput: s.cwd,
819
+ behaviorCwd,
820
+ chainSkills,
821
+ outputBaseDir,
822
+ parallelOutputNamespace,
823
+ resolvedBehavior,
824
+ });
825
+ const { stepCwd, instructionCwd, readExistenceCwd, behavior, namespaceOutputPath, outputPath, skillNames } = launchPlan;
824
826
  const { resolved: resolvedSkills, missing: missingSkills } = resolveSkillsWithFallback(
825
827
  skillNames,
826
828
  stepCwd,
@@ -845,7 +847,6 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
845
847
  const isFirstProgressAgent = behavior.progress && !progressPrecreated && !progressInstructionCreated;
846
848
  if (behavior.progress) progressInstructionCreated = true;
847
849
  const progressInstructions = buildChainInstructions({ ...behavior, output: false, reads: false }, progressDir, isFirstProgressAgent);
848
- const outputPath = resolveSingleOutputPath(behavior.output, ctx.cwd, instructionCwd, outputBaseDir);
849
850
  if (!namespaceOutputPath) systemPrompt = injectOutputPathSystemPrompt(systemPrompt, outputPath, a);
850
851
  const validationError = validateFileOnlyOutputMode(behavior.outputMode, outputPath, `Async step (${s.agent})`);
851
852
  if (validationError) throw new AsyncStartValidationError(validationError);
@@ -868,6 +869,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
868
869
  const thinkingOverride = flatIndex === undefined ? undefined : thinkingOverridesByFlatIndex?.[flatIndex];
869
870
  const effectiveThinking = externalRunner ? undefined : thinkingOverride ?? a.thinking;
870
871
  const model = externalRunner ? undefined : applyThinkingSuffix(primaryModel, effectiveThinking, thinkingOverride !== undefined);
872
+ const contextLimit = model ? findModelInfo(model, availableModels, a.modelProvider ?? ctx.currentModelProvider)?.contextWindow : undefined;
871
873
  const thinkingCeiling = externalRunner ? undefined : intersectThinkingCeilings(
872
874
  params.thinkingCeiling,
873
875
  a.maxThinking,
@@ -899,6 +901,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
899
901
  const fast = s.fast ?? params.fast ?? a.fast;
900
902
  const toolPlan = resolvePiLaunchToolPlan({
901
903
  tools: a.tools,
904
+ allowNestedSubagents: a.allowNestedSubagents,
902
905
  extensions: a.extensions,
903
906
  subagentOnlyExtensions: a.subagentOnlyExtensions,
904
907
  mcpDirectTools: a.mcpDirectTools,
@@ -912,6 +915,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
912
915
  inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
913
916
  agentName: a.name,
914
917
  permissionRules,
918
+ runtimeSnapshotHost: ctx.pi,
915
919
  });
916
920
  const launchResolvedExtensions = externalRunner ? undefined : projectLaunchResolvedChildExtensions(toolPlan);
917
921
  if (externalRunner && permissionRules) {
@@ -946,22 +950,29 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
946
950
  outputName: s.as,
947
951
  structured: Boolean(s.outputSchema),
948
952
  cwd: stepCwd,
953
+ requestedCwd: s.cwd ?? stepCwd,
949
954
  model,
955
+ ...(contextLimit !== undefined ? { contextLimit } : {}),
950
956
  ...(fast !== undefined ? { fast } : {}),
951
957
  thinking: resolveEffectiveThinking(model, effectiveThinking),
952
958
  ...(thinkingCeiling ? { thinkingCeiling } : {}),
953
959
  launchResolvedExtensions,
960
+ ...(toolPlan.mcpConfig ? { mcpConfig: toolPlan.mcpConfig } : {}),
961
+ ...(toolPlan.runtimeServerNames ? { runtimeServerNames: toolPlan.runtimeServerNames } : {}),
954
962
  modelCandidates: externalRunner ? undefined : modelCandidates,
955
963
  ...(primaryModelFromParent ? { skipPrimaryModelVerification: true } : {}),
956
964
  ...(availableModels && availableModels.length > 0 ? { modelVerificationRegistry: availableModels } : {}),
957
965
  tools: a.tools,
966
+ allowNestedSubagents: a.allowNestedSubagents,
958
967
  extensions: a.extensions,
959
968
  subagentOnlyExtensions: a.subagentOnlyExtensions,
960
969
  mcpDirectTools: a.mcpDirectTools,
970
+ mutationTools: a.mutationTools,
961
971
  completionGuard: a.completionGuard,
962
972
  systemPrompt,
963
973
  systemPromptMode: a.systemPromptMode,
964
974
  inheritProjectContext: a.inheritProjectContext,
975
+ inheritGlobalContext: a.inheritGlobalContext,
965
976
  inheritSkills: a.inheritSkills,
966
977
  skills: resolvedSkills.map((r) => r.name),
967
978
  outputPath,
@@ -972,6 +983,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
972
983
  timeoutMs: a.defaultTimeoutMs ?? DEFAULT_ASYNC_TIMEOUT_MS,
973
984
  toolTimeoutMs: resolvedToolTimeout.toolTimeoutMs,
974
985
  waitToolEnabled: params.waitToolEnabled,
986
+ waitToolDefaultTimeoutMs: params.waitToolDefaultTimeoutMs,
975
987
  effectiveAcceptance: resolveEffectiveAcceptance({
976
988
  explicit: s.acceptance,
977
989
  agentName: s.agent,
@@ -1098,6 +1110,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
1098
1110
  index: params.attachRoot.index,
1099
1111
  },
1100
1112
  inheritProjectContext: false,
1113
+ inheritGlobalContext: false,
1101
1114
  inheritSkills: false,
1102
1115
  }, ...builtSteps]
1103
1116
  : builtSteps;
@@ -1198,6 +1211,7 @@ export function executeAsyncChain(
1198
1211
  dynamicFanoutMaxItems: params.dynamicFanoutMaxItems,
1199
1212
  maxSubagentDepth,
1200
1213
  waitToolEnabled: params.waitToolEnabled,
1214
+ waitToolDefaultTimeoutMs: params.waitToolDefaultTimeoutMs,
1201
1215
  worktreeBaseDir,
1202
1216
  asyncDir,
1203
1217
  fast: params.fast,
@@ -1219,7 +1233,6 @@ export function executeAsyncChain(
1219
1233
  }
1220
1234
  const { steps, runnerCwd, workflowGraph, eventChain } = built;
1221
1235
  const deadlineAt = params.timeoutMs !== undefined ? Date.now() + params.timeoutMs : undefined;
1222
- const initialTurnBudget = params.turnBudget ? initialTurnBudgetState(params.turnBudget) : undefined;
1223
1236
  const initialUsageBudget = usageBudgetState(params.usageBudget, undefined);
1224
1237
  let childTargetIndex = 0;
1225
1238
  const childIntercomTargets = childIntercomTarget ? steps.flatMap((step) => {
@@ -1276,7 +1289,6 @@ export function executeAsyncChain(
1276
1289
  worktreeSetupHookTimeoutMs,
1277
1290
  worktreeBaseDir,
1278
1291
  controlConfig,
1279
- turnBudget: params.turnBudget,
1280
1292
  toolBudget: params.toolBudget,
1281
1293
  usageBudget: params.usageBudget,
1282
1294
  controlIntercomTarget,
@@ -1395,7 +1407,6 @@ export function executeAsyncChain(
1395
1407
  chainStepCount: eventChain.length,
1396
1408
  parallelGroups,
1397
1409
  ...(params.timeoutMs !== undefined ? { timeoutMs: params.timeoutMs, deadlineAt } : {}),
1398
- ...(initialTurnBudget ? { turnBudget: initialTurnBudget } : {}),
1399
1410
  startedAt: now,
1400
1411
  lastUpdate: now,
1401
1412
  ...(capabilityCeiling ? { capabilityCeiling } : {}),
@@ -1426,7 +1437,6 @@ export function executeAsyncChain(
1426
1437
  asyncDir,
1427
1438
  ...(sessionRoot ? { sessionRoot } : {}),
1428
1439
  ...(params.timeoutMs !== undefined ? { timeoutMs: params.timeoutMs, deadlineAt } : {}),
1429
- ...(initialTurnBudget ? { turnBudget: initialTurnBudget } : {}),
1430
1440
  ...(initialUsageBudget ? { usageBudget: initialUsageBudget } : {}),
1431
1441
  ...(capabilityCeiling ? { capabilityCeiling } : {}),
1432
1442
  ...(params.parentWorkflowRunId ? { parentWorkflowRunId: params.parentWorkflowRunId } : {}),
@@ -1443,7 +1453,7 @@ export function executeAsyncChain(
1443
1453
 
1444
1454
  return {
1445
1455
  content: [{ type: "text", text: formatAsyncStartedMessage(`Async ${resultMode}: ${chainDesc} [${id}]`, ctx.interactive === true) }],
1446
- details: { mode: resultMode, runId: id, results: [], asyncId: id, asyncDir, workflowGraph, ...(capabilityCeiling ? { capabilityCeiling } : {}), ...(params.parentWorkflowRunId ? { parentWorkflowRunId: params.parentWorkflowRunId } : {}), ...(params.workflowKey ? { workflowKey: params.workflowKey } : {}), ...(params.timeoutMs !== undefined ? { timeoutMs: params.timeoutMs, deadlineAt } : {}), ...(params.turnBudget ? { turnBudget: params.turnBudget } : {}), ...(params.toolBudget ? { toolBudget: params.toolBudget } : {}), ...(initialUsageBudget ? { usageBudget: initialUsageBudget } : {}) },
1456
+ details: { mode: resultMode, runId: id, results: [], asyncId: id, asyncDir, workflowGraph, ...(capabilityCeiling ? { capabilityCeiling } : {}), ...(params.parentWorkflowRunId ? { parentWorkflowRunId: params.parentWorkflowRunId } : {}), ...(params.workflowKey ? { workflowKey: params.workflowKey } : {}), ...(params.timeoutMs !== undefined ? { timeoutMs: params.timeoutMs, deadlineAt } : {}), ...(params.toolBudget ? { toolBudget: params.toolBudget } : {}), ...(initialUsageBudget ? { usageBudget: initialUsageBudget } : {}) },
1447
1457
  };
1448
1458
  }
1449
1459
 
@@ -1478,6 +1488,13 @@ export function executeAsyncSingle(
1478
1488
  childIntercomTarget,
1479
1489
  nestedRoute,
1480
1490
  } = params;
1491
+ let lane: WorkflowLaneMetadata | undefined;
1492
+ try {
1493
+ lane = normalizeWorkflowLaneMetadata(params.lane, "lane");
1494
+ assertWorkflowLaneKey(lane, params.workflowKey, "lane");
1495
+ } catch (error) {
1496
+ return formatAsyncStartError("single", error instanceof Error ? error.message : String(error));
1497
+ }
1481
1498
  const task = params.task ?? "";
1482
1499
  let extensionBindings: ExtensionBindings | undefined;
1483
1500
  try {
@@ -1582,6 +1599,7 @@ export function executeAsyncSingle(
1582
1599
  );
1583
1600
  const effectiveThinking = externalRunner ? undefined : params.thinkingOverride ?? agentConfig.thinking;
1584
1601
  const model = externalRunner ? undefined : applyThinkingSuffix(primaryModel, effectiveThinking, params.thinkingOverride !== undefined);
1602
+ const contextLimit = model ? findModelInfo(model, availableModels, agentConfig.modelProvider ?? ctx.currentModelProvider)?.contextWindow : undefined;
1585
1603
  const thinkingCeiling = externalRunner ? undefined : intersectThinkingCeilings(
1586
1604
  params.thinkingCeiling,
1587
1605
  agentConfig.maxThinking,
@@ -1610,7 +1628,6 @@ export function executeAsyncSingle(
1610
1628
  });
1611
1629
  if (resolvedToolTimeout.error) return formatAsyncStartError("single", resolvedToolTimeout.error);
1612
1630
  const toolTimeoutMs = resolvedToolTimeout.toolTimeoutMs;
1613
- const initialTurnBudget = params.turnBudget ? initialTurnBudgetState(params.turnBudget) : undefined;
1614
1631
  const initialUsageBudget = usageBudgetState(params.usageBudget, undefined);
1615
1632
  const resolvedSessionDir = params.sessionDir ?? (sessionRoot ? path.join(sessionRoot, `async-${id}`) : undefined);
1616
1633
  const structuredOutput = params.structuredOutputSchema
@@ -1633,9 +1650,9 @@ export function executeAsyncSingle(
1633
1650
  return formatAsyncStartError("single", error instanceof Error ? error.message : String(error));
1634
1651
  }
1635
1652
  }
1636
- const effectiveSystemPrompt = appendTurnBudgetSystemPrompt(systemPrompt, params.turnBudget);
1637
1653
  const toolPlan = resolvePiLaunchToolPlan({
1638
1654
  tools: agentConfig.tools,
1655
+ allowNestedSubagents: agentConfig.allowNestedSubagents,
1639
1656
  extensions: agentConfig.extensions,
1640
1657
  subagentOnlyExtensions: agentConfig.subagentOnlyExtensions,
1641
1658
  mcpDirectTools: agentConfig.mcpDirectTools,
@@ -1649,6 +1666,7 @@ export function executeAsyncSingle(
1649
1666
  inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
1650
1667
  agentName: agentConfig.name,
1651
1668
  permissionRules: resolvePermissionRules(ctx.permissions, agentConfig.permissions),
1669
+ runtimeSnapshotHost: ctx.pi,
1652
1670
  });
1653
1671
  const launchResolvedExtensions = externalRunner ? undefined : projectLaunchResolvedChildExtensions(toolPlan);
1654
1672
  if (!externalRunner) {
@@ -1673,9 +1691,10 @@ export function executeAsyncSingle(
1673
1691
  ...((params.fast ?? agentConfig.fast) !== undefined ? { fast: params.fast ?? agentConfig.fast } : {}),
1674
1692
  ...(resolveEffectiveThinking(model, effectiveThinking) ? { thinking: resolveEffectiveThinking(model, effectiveThinking) } : {}),
1675
1693
  ...(thinkingCeiling ? { thinkingCeiling } : {}),
1676
- systemPrompt: effectiveSystemPrompt,
1694
+ systemPrompt,
1677
1695
  systemPromptMode: agentConfig.systemPromptMode,
1678
1696
  inheritProjectContext: agentConfig.inheritProjectContext,
1697
+ inheritGlobalContext: agentConfig.inheritGlobalContext,
1679
1698
  inheritSkills: agentConfig.inheritSkills,
1680
1699
  skills: resolvedSkills.map((skill) => skill.name),
1681
1700
  tools: toolPlan.effectiveToolAllowlist,
@@ -1698,6 +1717,7 @@ export function executeAsyncSingle(
1698
1717
  const recoveryAgentConfig = params.recoveryAgentConfig ?? agentConfig;
1699
1718
  const recoveryDescriptor: SteeringRecoveryDescriptor = {
1700
1719
  version: 1,
1720
+ ...(lane ? { lane } : {}),
1701
1721
  launchContractDigest,
1702
1722
  ...(extensionBindings ? { extensionBindings } : {}),
1703
1723
  runFanoutBudget,
@@ -1715,12 +1735,15 @@ export function executeAsyncSingle(
1715
1735
  ...(effectiveThinking ? { thinking: resolveEffectiveThinking(model, effectiveThinking) } : {}),
1716
1736
  ...(thinkingCeiling ? { thinkingCeiling } : {}),
1717
1737
  ...(recoveryAgentConfig.tools ? { tools: [...recoveryAgentConfig.tools] } : {}),
1738
+ ...(recoveryAgentConfig.allowNestedSubagents !== undefined ? { allowNestedSubagents: recoveryAgentConfig.allowNestedSubagents } : {}),
1718
1739
  ...(recoveryAgentConfig.extensions ? { extensions: [...recoveryAgentConfig.extensions] } : {}),
1719
1740
  ...(recoveryAgentConfig.subagentOnlyExtensions ? { subagentOnlyExtensions: [...recoveryAgentConfig.subagentOnlyExtensions] } : {}),
1720
1741
  ...(recoveryAgentConfig.mcpDirectTools ? { mcpDirectTools: [...recoveryAgentConfig.mcpDirectTools] } : {}),
1742
+ ...(recoveryAgentConfig.mutationTools ? { mutationTools: [...recoveryAgentConfig.mutationTools] } : {}),
1721
1743
  ...(recoveryAgentConfig.systemPrompt ? { systemPrompt: recoveryAgentConfig.systemPrompt } : {}),
1722
1744
  systemPromptMode: recoveryAgentConfig.systemPromptMode,
1723
1745
  inheritProjectContext: recoveryAgentConfig.inheritProjectContext,
1746
+ inheritGlobalContext: recoveryAgentConfig.inheritGlobalContext,
1724
1747
  inheritSkills: recoveryAgentConfig.inheritSkills,
1725
1748
  ...(resolvedSkills.length ? { skills: resolvedSkills.map((skill) => skill.name) } : {}),
1726
1749
  ...(recoveryAgentConfig.skillPath ? { skillPath: [...recoveryAgentConfig.skillPath] } : {}),
@@ -1735,7 +1758,6 @@ export function executeAsyncSingle(
1735
1758
  ...(params.context ? { context: params.context } : {}),
1736
1759
  ...(params.intercomBridge !== undefined ? { intercomBridge: params.intercomBridge } : {}),
1737
1760
  ...(deadlineAt !== undefined ? { absoluteDeadlineAt: deadlineAt } : {}),
1738
- ...(initialTurnBudget ? { initialTurnBudget: { maxTurns: initialTurnBudget.maxTurns, graceTurns: initialTurnBudget.graceTurns } } : {}),
1739
1761
  ...(resolvedToolBudget.budget ? { initialToolBudget: resolvedToolBudget.budget } : {}),
1740
1762
  maxSubagentDepth: resolveChildMaxSubagentDepth(maxSubagentDepth, recoveryAgentConfig.maxSubagentDepth),
1741
1763
  ...(maxOutput ? { maxOutput } : {}),
@@ -1770,7 +1792,9 @@ export function executeAsyncSingle(
1770
1792
  ...(params.externalJobFollowUp ? { externalJobFollowUp: params.externalJobFollowUp } : {}),
1771
1793
  ...(params.context ? { context: params.context } : {}),
1772
1794
  cwd: runnerCwd,
1795
+ requestedCwd: params.requestedCwd ?? runnerCwd,
1773
1796
  model,
1797
+ ...(contextLimit !== undefined ? { contextLimit } : {}),
1774
1798
  ...(params.fast ?? agentConfig.fast ? { fast: params.fast ?? agentConfig.fast } : {}),
1775
1799
  thinking: resolveEffectiveThinking(model, effectiveThinking),
1776
1800
  ...(thinkingCeiling ? { thinkingCeiling } : {}),
@@ -1778,20 +1802,27 @@ export function executeAsyncSingle(
1778
1802
  ...(params.modelOverrideFromParent ? { skipPrimaryModelVerification: true } : {}),
1779
1803
  ...(availableModels && availableModels.length > 0 ? { modelVerificationRegistry: availableModels } : {}),
1780
1804
  tools: agentConfig.tools,
1805
+ allowNestedSubagents: agentConfig.allowNestedSubagents,
1781
1806
  extensions: agentConfig.extensions,
1782
1807
  subagentOnlyExtensions: agentConfig.subagentOnlyExtensions,
1783
1808
  mcpDirectTools: agentConfig.mcpDirectTools,
1809
+ ...(toolPlan.mcpConfig ? { mcpConfig: toolPlan.mcpConfig } : {}),
1810
+ ...(toolPlan.runtimeServerNames ? { runtimeServerNames: toolPlan.runtimeServerNames } : {}),
1811
+ mutationTools: agentConfig.mutationTools,
1784
1812
  completionGuard: agentConfig.completionGuard,
1785
1813
  systemPrompt,
1786
1814
  systemPromptMode: agentConfig.systemPromptMode,
1787
1815
  inheritProjectContext: agentConfig.inheritProjectContext,
1816
+ inheritGlobalContext: agentConfig.inheritGlobalContext,
1788
1817
  inheritSkills: agentConfig.inheritSkills,
1789
1818
  skills: resolvedSkills.map((r) => r.name),
1790
1819
  outputPath,
1820
+ ...(params.outputClaimPath ? { outputClaimPath: params.outputClaimPath } : {}),
1791
1821
  outputMode,
1792
1822
  ...(!externalRunner && sessionFile ? { sessionFile } : {}),
1793
1823
  maxSubagentDepth: resolveChildMaxSubagentDepth(maxSubagentDepth, agentConfig.maxSubagentDepth),
1794
1824
  waitToolEnabled: params.waitToolEnabled,
1825
+ waitToolDefaultTimeoutMs: params.waitToolDefaultTimeoutMs,
1795
1826
  ...(params.agentContract ? { agentContract: params.agentContract } : {}),
1796
1827
  definitionDigest: agentDefinitionDigest(agentConfig),
1797
1828
  launchBindingTask: task,
@@ -1803,6 +1834,7 @@ export function executeAsyncSingle(
1803
1834
  ...(params.structuredOutputSchema ? { structuredOutputSchema: params.structuredOutputSchema } : {}),
1804
1835
  ...(resolvedToolBudget.budget ? { toolBudget: resolvedToolBudget.budget } : {}),
1805
1836
  ...(params.worktree === true ? { worktree: true } : {}),
1837
+ ...(lane ? { lane } : {}),
1806
1838
  },
1807
1839
  ],
1808
1840
  resultPath: params.parentWorkflowRunId !== undefined && (params.revivalLease !== undefined || params.workflowAwaitAsync === true)
@@ -1828,7 +1860,6 @@ export function executeAsyncSingle(
1828
1860
  timeoutMs,
1829
1861
  deadlineAt,
1830
1862
  toolTimeoutMs,
1831
- turnBudget: params.turnBudget,
1832
1863
  toolBudget: params.toolBudget,
1833
1864
  usageBudget: params.usageBudget,
1834
1865
  controlIntercomTarget,
@@ -1839,6 +1870,7 @@ export function executeAsyncSingle(
1839
1870
  runFanoutBudget,
1840
1871
  ...(params.parentWorkflowRunId ? { parentWorkflowRunId: params.parentWorkflowRunId } : {}),
1841
1872
  ...(params.workflowKey ? { workflowKey: params.workflowKey } : {}),
1873
+ ...(lane ? { lane } : {}),
1842
1874
  ...(params.revivalLease ? { revivalLease: params.revivalLease } : {}),
1843
1875
  nestedRoute: nestedRoute ?? inheritedNestedRoute,
1844
1876
  nestedSelf: inheritedNestedRoute && nestedAddress ? {
@@ -1861,10 +1893,12 @@ export function executeAsyncSingle(
1861
1893
  lastUpdate: initialStatusAt,
1862
1894
  currentStep: 0,
1863
1895
  chainStepCount: 1,
1864
- steps: [{ agent, status: "pending" }],
1896
+ ...(lane ? { lane } : {}),
1897
+ steps: [{ agent, status: "pending", ...(lane ? { lane } : {}), ...(model ? { model } : {}), ...(contextLimit !== undefined ? { contextLimit } : {}) }],
1865
1898
  },
1866
1899
  path.join(asyncDir, "status.json"),
1867
1900
  (proof) => emitProcessTerminalEvent(ctx, proof),
1901
+ params.requestedCwd ?? runnerCwd,
1868
1902
  );
1869
1903
  } catch (error) {
1870
1904
  params.activeAsyncCapacity?.rollback();
@@ -1910,7 +1944,6 @@ export function executeAsyncSingle(
1910
1944
  agents: [agent],
1911
1945
  chainStepCount: 1,
1912
1946
  ...(timeoutMs !== undefined ? { timeoutMs, deadlineAt } : {}),
1913
- ...(initialTurnBudget ? { turnBudget: initialTurnBudget } : {}),
1914
1947
  startedAt: now,
1915
1948
  lastUpdate: now,
1916
1949
  ...(capabilityCeiling ? { capabilityCeiling } : {}),
@@ -1938,7 +1971,6 @@ export function executeAsyncSingle(
1938
1971
  ...(params.parentWorkflowRunId ? { parentWorkflowRunId: params.parentWorkflowRunId } : {}),
1939
1972
  ...(params.workflowKey ? { workflowKey: params.workflowKey } : {}),
1940
1973
  ...(timeoutMs !== undefined ? { timeoutMs, deadlineAt } : {}),
1941
- ...(initialTurnBudget ? { turnBudget: initialTurnBudget } : {}),
1942
1974
  ...(initialUsageBudget ? { usageBudget: initialUsageBudget } : {}),
1943
1975
  ...(capabilityCeiling ? { capabilityCeiling } : {}),
1944
1976
  nestedRoute,
@@ -1947,6 +1979,6 @@ export function executeAsyncSingle(
1947
1979
 
1948
1980
  return {
1949
1981
  content: [{ type: "text", text: formatAsyncStartedMessage(`Async: ${agent} [${id}]`, ctx.interactive === true) }],
1950
- details: { mode: "single", runId: id, results: [], asyncId: id, asyncDir, launchContractDigest, launchResolvedExtensions, ...(capabilityCeiling ? { capabilityCeiling } : {}), ...(params.context ? { context: params.context } : {}), ...(timeoutMs !== undefined ? { timeoutMs, deadlineAt } : {}), ...(params.turnBudget ? { turnBudget: params.turnBudget } : {}), ...(params.toolBudget ? { toolBudget: resolvedToolBudget.budget ?? params.toolBudget } : {}), ...(initialUsageBudget ? { usageBudget: initialUsageBudget } : {}) } as Details,
1982
+ details: { mode: "single", runId: id, results: [], asyncId: id, asyncDir, launchContractDigest, launchResolvedExtensions, ...(capabilityCeiling ? { capabilityCeiling } : {}), ...(params.context ? { context: params.context } : {}), ...(timeoutMs !== undefined ? { timeoutMs, deadlineAt } : {}), ...(params.toolBudget ? { toolBudget: resolvedToolBudget.budget ?? params.toolBudget } : {}), ...(initialUsageBudget ? { usageBudget: initialUsageBudget } : {}) } as Details,
1951
1983
  };
1952
1984
  }
@@ -25,6 +25,8 @@ import { listAsyncRuns, type AsyncRunSummary } from "./async-status.ts";
25
25
  import { EXTERNAL_JOB_BRIDGE_REQUEST_DIR, serviceExternalJobBridgeRequests } from "../shared/external-job-bridge.ts";
26
26
  import { shouldUseNativeFsWatch } from "../../shared/watch-strategy.ts";
27
27
  import { parseWorkflowChildSummary } from "../../workflows/workflow-child-summary.ts";
28
+ import { validHostStepNodes } from "../shared/host-step-status.ts";
29
+ import { withCachedUiContext } from "../../shared/extension-context.ts";
28
30
 
29
31
  interface AsyncJobTrackerOptions {
30
32
  completionRetentionMs?: number;
@@ -78,23 +80,19 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
78
80
  const watch = options.watch ?? fs.watch;
79
81
  const useNativeWatcher = () => shouldUseNativeFsWatch("async-job-tracker", options.platform);
80
82
  const terminalStatus = (status: string) => status === "complete" || status === "failed" || status === "paused" || status === "stopped";
83
+ const withLastUiContext = <T>(run: (ctx: ExtensionContext) => T): T | undefined => {
84
+ const cached = state.lastUiContext;
85
+ return withCachedUiContext(cached, () => {
86
+ if (state.lastUiContext === cached) state.lastUiContext = null;
87
+ }, run);
88
+ };
81
89
  const rerenderWidget = (ctx: ExtensionContext, jobs = Array.from(state.asyncJobs.values())) => {
82
90
  if (state.widgetsSuspended) return;
83
91
  renderWidget(ctx, options.widgetEnabled === false ? [] : jobs);
84
92
  (ctx.ui as { requestRender?: () => void }).requestRender?.();
85
93
  };
86
94
  const rerenderLastWidget = (jobs = Array.from(state.asyncJobs.values())) => {
87
- const ctx = state.lastUiContext;
88
- if (!ctx) return;
89
- try {
90
- if (ctx.hasUI) rerenderWidget(ctx, jobs);
91
- } catch (error) {
92
- if (error instanceof Error && error.message.includes("extension ctx is stale")) {
93
- state.lastUiContext = null;
94
- return;
95
- }
96
- throw error;
97
- }
95
+ withLastUiContext((ctx) => rerenderWidget(ctx, jobs));
98
96
  };
99
97
  const requestLastWidgetRender = () => {
100
98
  if (options.widgetEnabled === false) return;
@@ -147,6 +145,8 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
147
145
  currentStep: run.currentStep,
148
146
  chainStepCount: run.chainStepCount,
149
147
  parallelGroups: groups,
148
+ hostSteps: run.hostSteps,
149
+ preflight: run.preflight,
150
150
  steps: visibleSteps,
151
151
  stepsTotal: visibleSteps.length,
152
152
  runningSteps: visibleSteps.filter((step) => step.status === "running").length,
@@ -342,7 +342,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
342
342
  };
343
343
 
344
344
  const refreshJob = (job: AsyncJobState): boolean => {
345
- const widgetExpanded = state.lastUiContext?.hasUI ? state.lastUiContext.ui.getToolsExpanded?.() ?? false : false;
345
+ const widgetExpanded = withLastUiContext((ctx) => ctx.ui.getToolsExpanded?.() ?? false) ?? false;
346
346
  const widgetStateBefore = widgetRenderKey(job, widgetExpanded);
347
347
  let nestedRefreshFailed = false;
348
348
  const refreshNestedProjection = () => {
@@ -399,7 +399,9 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
399
399
  job.mode = status.mode;
400
400
  job.parentWorkflowRunId = status.parentWorkflowRunId ?? job.parentWorkflowRunId;
401
401
  job.workflowKey = status.workflowKey ?? job.workflowKey;
402
+ job.lane = status.lane ?? job.lane;
402
403
  job.workflow = status.workflow ?? job.workflow;
404
+ job.hostSteps = validHostStepNodes(status.workflowGraph);
403
405
  const workflowChildren = parseWorkflowChildSummary(status.workflowChildren);
404
406
  if (workflowChildren && workflowChildren.workflowRunId !== status.runId) throw new Error("workflowChildren.workflowRunId does not match async status runId.");
405
407
  job.workflowChildren = workflowChildren ?? job.workflowChildren;