pi-subagents 0.57.0 → 0.58.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 (55) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/docs/agents.md +13 -6
  3. package/docs/extension-api.md +39 -0
  4. package/docs/models.md +3 -3
  5. package/docs/tool-reference.md +3 -2
  6. package/docs/workflows.md +24 -0
  7. package/package.json +1 -1
  8. package/skills/pi-subagents/SKILL.md +2 -0
  9. package/skills/pi-subagents/references/execution-controls.md +1 -1
  10. package/skills/pi-subagents/references/management-authoring-rpc.md +1 -0
  11. package/skills/pi-subagents/references/prompting-and-roles.md +2 -2
  12. package/src/agents/agent-management.ts +23 -0
  13. package/src/agents/agent-serializer.ts +5 -0
  14. package/src/agents/agents.ts +38 -3
  15. package/src/agents/runtime-agent-events.ts +70 -0
  16. package/src/agents/runtime-agent-registry.ts +10 -2
  17. package/src/api/agents.ts +10 -5
  18. package/src/api/preflight.ts +26 -1
  19. package/src/extension/index.ts +15 -4
  20. package/src/extension/rpc.ts +35 -14
  21. package/src/extension/schemas.ts +2 -2
  22. package/src/extension/tool-description.ts +8 -4
  23. package/src/integrations/herdr-status.ts +5 -0
  24. package/src/runs/background/async-execution.ts +29 -12
  25. package/src/runs/background/async-resume.ts +6 -2
  26. package/src/runs/background/async-status.ts +2 -0
  27. package/src/runs/background/chain-append.ts +2 -0
  28. package/src/runs/background/notify.ts +26 -3
  29. package/src/runs/background/result-delivery-ownership.ts +45 -0
  30. package/src/runs/background/result-watcher.ts +27 -10
  31. package/src/runs/background/subagent-runner.ts +71 -21
  32. package/src/runs/foreground/execution.ts +20 -1
  33. package/src/runs/foreground/subagent-executor.ts +57 -26
  34. package/src/runs/foreground/workflow-detach-reconcile.ts +128 -17
  35. package/src/runs/shared/completion-guard.ts +4 -3
  36. package/src/runs/shared/dynamic-fanout.ts +3 -3
  37. package/src/runs/shared/fast-mode-extension.ts +5 -5
  38. package/src/runs/shared/launch-cwd.ts +16 -0
  39. package/src/runs/shared/long-running-guard.ts +2 -1
  40. package/src/runs/shared/mcp-config-sources.ts +386 -0
  41. package/src/runs/shared/mcp-direct-tool-allowlist.ts +155 -42
  42. package/src/runs/shared/model-exclusions.ts +12 -0
  43. package/src/runs/shared/model-fallback.ts +16 -4
  44. package/src/runs/shared/parallel-utils.ts +8 -1
  45. package/src/runs/shared/pi-args.ts +61 -6
  46. package/src/runs/shared/single-output.ts +17 -0
  47. package/src/runs/shared/subagent-prompt-runtime.ts +85 -9
  48. package/src/shared/formatters.ts +6 -0
  49. package/src/shared/launch-contract.ts +4 -0
  50. package/src/shared/types.ts +42 -1
  51. package/src/shared/utils.ts +6 -29
  52. package/src/slash/subagents-admin.ts +3 -0
  53. package/src/tui/render.ts +14 -6
  54. package/src/workflows/scripted-workflow.ts +52 -5
  55. package/src/workflows/workflow-receipt.ts +39 -4
@@ -5,8 +5,9 @@ import { getAgentDir, getProjectConfigDir } from "../shared/utils.ts";
5
5
 
6
6
  const CUSTOM_TOOL_DESCRIPTION_FILE = "subagent-tool-description.md";
7
7
  const CUSTOM_TOOL_DESCRIPTION_MAX_BYTES = 50 * 1024;
8
+ const EXTERNAL_CLI_RUNNER_GUIDANCE = "External CLI agents (codex-exec, codex-exec-writer, claude-code, claude-code-writer, cursor-agent, cursor-agent-writer) use their own runner contract and do not support native Pi child options such as model override, structured output, acceptance/agent contract, tool budget, fast mode, fork context, skills, or native Pi tools unless the runner explicitly implements them.";
8
9
 
9
- export const DEFAULT_SUBAGENT_TOOL_DESCRIPTION = `Delegate to configured subagents. For execution, omit action and use {agent, task?} for one child, workflowScript for inline orchestration, or workflowScriptPath to load a script from the request cwd. The script inputs are mutually exclusive. Use action:'validate' with either script input to check it without launching children. For multi-step or parallel work, make exactly one top-level subagent call with async:true; launch children only inside that workflow and do not make another top-level call for them. Use runs.run('key',{agent,task}) for one child, await runs.all([{key:'a',agent:'reviewer',task:'...'},{key:'b',agent:'reviewer',task:'...'}]) for ordinary parallel children, and read its ordered array result with indexes, destructuring, or .map(...), not by key property. Use action only for management/control. Use guide or the pi-subagents skill for advanced workflow details.`;
10
+ export const DEFAULT_SUBAGENT_TOOL_DESCRIPTION = `Delegate to configured subagents. For execution, omit action and use {agent, task?} for one child, workflowScript for inline orchestration, or workflowScriptPath to load a script from the request cwd. The script inputs are mutually exclusive. Use action:'validate' with either script input to check it without launching children. For multi-step or parallel work, make exactly one top-level subagent call with async:true; launch children only inside that workflow and do not make another top-level call for them. Use runs.run('key',{agent,task}) for one child, await runs.all([{key:'a',agent:'reviewer',task:'...'},{key:'b',agent:'reviewer',task:'...'}]) for ordinary parallel children, and read its ordered array result with indexes, destructuring, or .map(...), not by key property. ${EXTERNAL_CLI_RUNNER_GUIDANCE} Use action only for management/control. Use guide or the pi-subagents skill for advanced workflow details.`;
10
11
 
11
12
  export const SUBAGENT_TOOL_PROMPT_SNIPPET = "Delegate to subagents; orchestrate in one workflowScript call.";
12
13
 
@@ -16,7 +17,8 @@ export const SUBAGENT_TOOL_PROMPT_GUIDELINES = [
16
17
  "workflowScript means exactly one top-level subagent tool call with async:true. Inside it, use runs.run/runs.all to launch children; do not make another top-level subagent call for those children.",
17
18
  "For ordinary parallel work, use await runs.all([{key,agent,task}, ...]); it resolves to an ordered array, not a key map, so use results[0], destructuring, or results.map(...), not results.<key>. Do not read .output from unawaited runs.run launches. Stored runs.run promises are only for advanced rolling fanout and each must later be observed with direct await, Promise.race, or Promise.all.",
18
19
  "Keep one writer per cwd/worktree unless writers run in isolated worktrees.",
19
- "To pass an explicit model to a child, first call { action: \"models\" } and copy an exact provider/id (e.g. openai-codex/gpt-5.6-sol); bare ids resolve only when unique in the registry, and agent names (gpt-pro, advisor) are not model ids.",
20
+ "To pass an explicit model to a child, first call { action: \"models\" } and copy an exact provider/id (e.g. openai-codex/gpt-5.6-sol); bare ids resolve only when unique in the registry, and agent names (gpt-pro, advisor) are not model ids. Set per-run thinking with a suffix on the model string (e.g. openai-codex/gpt-5.6-sol:high; off/minimal/low/medium/high/xhigh/max); the suffix wins over the agent's thinking default. The thinking field only applies to action='watchdog.configure' and is ignored on dispatch.",
21
+ EXTERNAL_CLI_RUNNER_GUIDANCE,
20
22
  "Use guide or the pi-subagents skill for advanced scheduling, missions, steering, and retention.",
21
23
  ];
22
24
 
@@ -32,8 +34,9 @@ export const SUBAGENT_SAFETY_GUIDANCE = `SAFETY-CRITICAL SUBAGENT GUIDANCE:
32
34
  export const FULL_SUBAGENT_TOOL_DESCRIPTION = `Run one child with { agent, task? }; use { workflowScript } for inline orchestration or { workflowScriptPath } to load it from the request cwd. The script inputs are mutually exclusive. Omit action for execution. Use action only for management/control actions.
33
35
 
34
36
  EXECUTION:
37
+ • ${EXTERNAL_CLI_RUNNER_GUIDANCE}
35
38
  • Before executing, use { action: "list" } and run only executable/non-disabled configured agents.
36
- • When passing an explicit model to a child (on the call or a runs.run/runs.all item), first call { action: "models" } and copy an exact provider/id; bare ids resolve only when unique in the registry, and agent names (e.g. gpt-pro, advisor) are not model ids.
39
+ • When passing an explicit model to a child (on the call or a runs.run/runs.all item), first call { action: "models" } and copy an exact provider/id; bare ids resolve only when unique in the registry, and agent names (e.g. gpt-pro, advisor) are not model ids. Set per-run thinking with a suffix on the model string (e.g. provider/id:high; off/minimal/low/medium/high/xhigh/max); the suffix wins over the agent's thinking default. The thinking field only applies to action='watchdog.configure' and is ignored on dispatch.
37
40
  • SINGLE CHILD: { agent:"worker", task:"..." }. This structured form starts exactly one direct child. Fields such as model, context, cwd, worktree, output, budgets, acceptance, and async apply to that child. Do not combine agent/task with action, workflowScript, or workflowScriptPath.
38
41
  • WORKFLOW SCRIPT: { workflowScript: "return runs.run('main', {agent:'worker', task:'...'})" }. Use stable-key runs.run for one child and await runs.all([{key,agent,task}, ...]) for ordinary parallel children. runs.all resolves to an ordered array, not a key map, so use results[0], array destructuring, or results.map((result) => result.output), not results.<key>. Do not read .output from unawaited runs.run launches. Stored runs.run promises are only for advanced rolling fanout and each must later be observed with direct await, Promise.race, or Promise.all. Ordinary JavaScript provides sequence, branching, filtering, retries, and aggregation. workflowScript is an ordinary JavaScript statement body, so use an explicit return for a useful result. Use top-level await, plain helper functions, or explicit Promise chains; nested async function, arrow, and method helpers are rejected. For task text with Markdown fences or shell blocks, build quoted lines instead of nesting raw template literals: \`const task=["Run:","\`\`\`bash","npm test","\`\`\`"].join("\\n")\`. Scripts normally start async unless config sets asyncByDefault:false; set async:true explicitly when async behavior matters. Pass async:false only when the parent must block until completion, never for final reviews or gates. Same-repo blocking workflows default to a live in-chat card; explicit live-card requires same-repository async:false, so async workflows should omit chatProgress or use auto/off. Workflow-level child controls default onto each runs.run launch, and explicit child fields override them. Use {action:"children.list"} to list recent retained workflow children with resumable/not-resumable reasons. Resume only rows reported resumable. For a simple follow-up or implementation challenge, use {action:"resume", id:"run-id", message:"..."}. Resume keeps the stored agent/model/tool contract. If no resumable child is listed, launch a same-role fallback challenge and label it as fallback. Inside workflowScript, continue one with runs.run(key, {resume:"run-id", task:"follow-up"}); workflow resumes wait for completed output, and loops must continue from each latest returned runId. Await runs.steer(key, message, {mode?, index?, ackTimeoutMs?}) to guide a prior keyed child without exposing its run id; receipts are queued, delivered, missed, or failed. Always await or return runs.steer. For repository mutation lanes, set worktree:true on the workflow or individual runs.run/runs.all item for managed isolation; each parallel child gets a separate worktree and handoff artifact. A workflow usageBudget is enforced once across the workflow. Available globals are runs.run, runs.all, runs.steer, runs.status, runs.ref/refs, emit, console, and standard JavaScript only. Workflows get async state.get(key) and state.set(key, JSONValue) through their automatic or explicit mission; mission:false workflows do not have a state global. Scripts cannot access filesystem, shell, arbitrary Pi tools, or host globals.
39
42
  • FILE SCRIPT: { workflowScriptPath:"workflows/review.js" }. Relative paths resolve against the request cwd. The host reads the file before the filesystem-free workflow sandbox starts. Do not combine this field with workflowScript.
@@ -52,8 +55,9 @@ ${SUBAGENT_SAFETY_GUIDANCE}`;
52
55
  export const COMPACT_SUBAGENT_TOOL_DESCRIPTION = `Run one child with { agent, task? }; use { workflowScript } for inline orchestration or { workflowScriptPath } to load it from the request cwd. The script inputs are mutually exclusive. Omit action for execution. Use action only for management/control actions.
53
56
 
54
57
  EXECUTE:
58
+ • ${EXTERNAL_CLI_RUNNER_GUIDANCE}
55
59
  • Call { action:"list" } first and use only executable/non-disabled agents.
56
- • Passing an explicit model? Call {action:"models"} first and copy an exact provider/id; bare ids resolve only when unique in the registry; agent names (e.g. gpt-pro, advisor) are not model ids.
60
+ • Passing an explicit model? Call {action:"models"} first and copy an exact provider/id; bare ids resolve only when unique in the registry; agent names (e.g. gpt-pro, advisor) are not model ids. Per-run thinking is a suffix on the model string (provider/id:high; off/minimal/low/medium/high/xhigh/max), and the suffix wins over the agent's thinking default; the thinking field only applies to action='watchdog.configure' and is ignored on dispatch.
57
61
  • SINGLE {agent:"worker",task:"..."} starts exactly one direct child. Fields apply to that child. Do not combine agent/task with action, workflowScript, or workflowScriptPath.
58
62
  • SCRIPT {workflowScript:"return runs.run('main', {agent:'worker', task:'...'})"}. Use stable-key runs.run for one child and await runs.all([{key,agent,task}, ...]) for ordinary parallel work. runs.all resolves to an ordered array, not a key map; use results[0], destructuring, or results.map(...), not results.<key>. Do not read .output from unawaited runs.run launches. Stored runs.run promises are only for advanced rolling fanout and each must later be observed with direct await, Promise.race, or Promise.all. Await runs.steer(key,message,options?) to guide a prior keyed child; it returns queued, delivered, missed, or failed and never accepts a raw run id. Always await or return steering calls. Use {action:"children.list"} for recent retained workflow children and resume only rows reported resumable. Use {action:"resume",id:"run-id",message:"..."} for a simple follow-up or challenge; resume keeps the stored agent/model/tool contract. If none is resumable, launch a same-role fallback challenge and label it as fallback. Inside workflowScript use runs.run(key,{resume:"run-id",task:"follow-up"}) when the script must wait for completion and continue from the latest returned runId. Workflows get async state.get/state.set through their automatic or explicit mission; mission:false does not. Scripts are ordinary JavaScript statement bodies; use explicit return for a useful result. Use top-level await, plain helper functions, or explicit Promise chains; nested async function, arrow, and method helpers are rejected. For task text with Markdown fences or shell blocks, build quoted lines instead of nesting raw template literals: \`const task=["Run:","\`\`\`bash","npm test","\`\`\`"].join("\\n")\`. Use JavaScript for sequence, branching, retries, and aggregation. For repository mutation lanes, use worktree:true on the workflow or runs.run/runs.all item for managed isolation. Scripts normally start async unless config sets asyncByDefault:false; set async:true explicitly when async behavior matters. async:false blocks the parent until completion and auto-enables a same-repo live chat card unless chatProgress is off; explicit live-card requires same-repository async:false, so async workflows should omit chatProgress or use auto/off.
59
63
  • FILE SCRIPT {workflowScriptPath:"workflows/review.js"} loads the script on the host relative to the request cwd before sandbox execution. Do not combine it with workflowScript.
@@ -59,6 +59,7 @@ export interface HerdrStatusBridge {
59
59
  * state. Also re-syncs runs that survived a reload/resume.
60
60
  */
61
61
  sessionStarted(input: { hasUI: boolean; runs: Iterable<HerdrStatusRun> }): void;
62
+ syncRuns(): void;
62
63
  agentStarted(): void;
63
64
  flush(): Promise<void>;
64
65
  dispose(): void;
@@ -377,6 +378,10 @@ export function registerHerdrStatusBridge(options: HerdrStatusBridgeOptions): He
377
378
  rootSession = true;
378
379
  replaceRuns(restoredRuns);
379
380
  },
381
+ syncRuns() {
382
+ if (!enabled || !rootSession || disposed) return;
383
+ refresh();
384
+ },
380
385
  async flush() {
381
386
  while (draining || pendingReport) await drainPromise;
382
387
  },
@@ -20,6 +20,7 @@ import { buildChainInstructions, isDynamicParallelStep, isParallelStep, resolveC
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";
@@ -27,7 +28,7 @@ import { PI_CODING_AGENT_PACKAGE_ROOT_ENV, PROMPT_REDACTED, resolveChildCwd } fr
27
28
  import { buildModelCandidates, inheritsParentModel, resolveEffectiveSubagentModel, resolveModelCandidate, 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";
@@ -212,6 +213,7 @@ interface AsyncSingleParams {
212
213
  recoveryAgentConfig?: AgentConfig;
213
214
  ctx: AsyncExecutionContext;
214
215
  cwd?: string;
216
+ requestedCwd?: string;
215
217
  maxOutput?: MaxOutputConfig;
216
218
  artifactsDir?: string;
217
219
  artifactConfig: ArtifactConfig;
@@ -512,20 +514,14 @@ export function emitProcessTerminalEvent(ctx: AsyncExecutionContext, proof: unkn
512
514
  }
513
515
  }
514
516
 
515
- function spawnRunner(cfg: object, suffix: string, cwd: string, initialStatus: Omit<AsyncStatus, "pid" | "processTerminal">, initialStatusPath: string, onProcessTerminal?: (proof: unknown) => void): SpawnRunnerResult {
517
+ function spawnRunner(cfg: object, suffix: string, cwd: string, initialStatus: Omit<AsyncStatus, "pid" | "processTerminal">, initialStatusPath: string, onProcessTerminal?: (proof: unknown) => void, requestedCwd = cwd): SpawnRunnerResult {
518
+ const cwdError = preflightLaunchCwd(requestedCwd, cwd);
519
+ if (cwdError) return { error: cwdError };
520
+
516
521
  if (!jitiCliPath) {
517
522
  return { error: "upstream jiti for TypeScript execution could not be found; ensure package dependencies are installed" };
518
523
  }
519
524
 
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
525
  fs.mkdirSync(TEMP_ROOT_DIR, { recursive: true });
530
526
  const cfgPath = getAsyncConfigPath(suffix);
531
527
  const runnerProcessInstanceId = randomUUID();
@@ -868,6 +864,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
868
864
  const thinkingOverride = flatIndex === undefined ? undefined : thinkingOverridesByFlatIndex?.[flatIndex];
869
865
  const effectiveThinking = externalRunner ? undefined : thinkingOverride ?? a.thinking;
870
866
  const model = externalRunner ? undefined : applyThinkingSuffix(primaryModel, effectiveThinking, thinkingOverride !== undefined);
867
+ const contextLimit = model ? findModelInfo(model, availableModels, a.modelProvider ?? ctx.currentModelProvider)?.contextWindow : undefined;
871
868
  const thinkingCeiling = externalRunner ? undefined : intersectThinkingCeilings(
872
869
  params.thinkingCeiling,
873
870
  a.maxThinking,
@@ -912,6 +909,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
912
909
  inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
913
910
  agentName: a.name,
914
911
  permissionRules,
912
+ runtimeSnapshotHost: ctx.pi,
915
913
  });
916
914
  const launchResolvedExtensions = externalRunner ? undefined : projectLaunchResolvedChildExtensions(toolPlan);
917
915
  if (externalRunner && permissionRules) {
@@ -946,11 +944,15 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
946
944
  outputName: s.as,
947
945
  structured: Boolean(s.outputSchema),
948
946
  cwd: stepCwd,
947
+ requestedCwd: s.cwd ?? stepCwd,
949
948
  model,
949
+ ...(contextLimit !== undefined ? { contextLimit } : {}),
950
950
  ...(fast !== undefined ? { fast } : {}),
951
951
  thinking: resolveEffectiveThinking(model, effectiveThinking),
952
952
  ...(thinkingCeiling ? { thinkingCeiling } : {}),
953
953
  launchResolvedExtensions,
954
+ ...(toolPlan.mcpConfig ? { mcpConfig: toolPlan.mcpConfig } : {}),
955
+ ...(toolPlan.runtimeServerNames ? { runtimeServerNames: toolPlan.runtimeServerNames } : {}),
954
956
  modelCandidates: externalRunner ? undefined : modelCandidates,
955
957
  ...(primaryModelFromParent ? { skipPrimaryModelVerification: true } : {}),
956
958
  ...(availableModels && availableModels.length > 0 ? { modelVerificationRegistry: availableModels } : {}),
@@ -958,10 +960,12 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
958
960
  extensions: a.extensions,
959
961
  subagentOnlyExtensions: a.subagentOnlyExtensions,
960
962
  mcpDirectTools: a.mcpDirectTools,
963
+ mutationTools: a.mutationTools,
961
964
  completionGuard: a.completionGuard,
962
965
  systemPrompt,
963
966
  systemPromptMode: a.systemPromptMode,
964
967
  inheritProjectContext: a.inheritProjectContext,
968
+ inheritGlobalContext: a.inheritGlobalContext,
965
969
  inheritSkills: a.inheritSkills,
966
970
  skills: resolvedSkills.map((r) => r.name),
967
971
  outputPath,
@@ -1098,6 +1102,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
1098
1102
  index: params.attachRoot.index,
1099
1103
  },
1100
1104
  inheritProjectContext: false,
1105
+ inheritGlobalContext: false,
1101
1106
  inheritSkills: false,
1102
1107
  }, ...builtSteps]
1103
1108
  : builtSteps;
@@ -1582,6 +1587,7 @@ export function executeAsyncSingle(
1582
1587
  );
1583
1588
  const effectiveThinking = externalRunner ? undefined : params.thinkingOverride ?? agentConfig.thinking;
1584
1589
  const model = externalRunner ? undefined : applyThinkingSuffix(primaryModel, effectiveThinking, params.thinkingOverride !== undefined);
1590
+ const contextLimit = model ? findModelInfo(model, availableModels, agentConfig.modelProvider ?? ctx.currentModelProvider)?.contextWindow : undefined;
1585
1591
  const thinkingCeiling = externalRunner ? undefined : intersectThinkingCeilings(
1586
1592
  params.thinkingCeiling,
1587
1593
  agentConfig.maxThinking,
@@ -1649,6 +1655,7 @@ export function executeAsyncSingle(
1649
1655
  inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
1650
1656
  agentName: agentConfig.name,
1651
1657
  permissionRules: resolvePermissionRules(ctx.permissions, agentConfig.permissions),
1658
+ runtimeSnapshotHost: ctx.pi,
1652
1659
  });
1653
1660
  const launchResolvedExtensions = externalRunner ? undefined : projectLaunchResolvedChildExtensions(toolPlan);
1654
1661
  if (!externalRunner) {
@@ -1676,6 +1683,7 @@ export function executeAsyncSingle(
1676
1683
  systemPrompt: effectiveSystemPrompt,
1677
1684
  systemPromptMode: agentConfig.systemPromptMode,
1678
1685
  inheritProjectContext: agentConfig.inheritProjectContext,
1686
+ inheritGlobalContext: agentConfig.inheritGlobalContext,
1679
1687
  inheritSkills: agentConfig.inheritSkills,
1680
1688
  skills: resolvedSkills.map((skill) => skill.name),
1681
1689
  tools: toolPlan.effectiveToolAllowlist,
@@ -1718,9 +1726,11 @@ export function executeAsyncSingle(
1718
1726
  ...(recoveryAgentConfig.extensions ? { extensions: [...recoveryAgentConfig.extensions] } : {}),
1719
1727
  ...(recoveryAgentConfig.subagentOnlyExtensions ? { subagentOnlyExtensions: [...recoveryAgentConfig.subagentOnlyExtensions] } : {}),
1720
1728
  ...(recoveryAgentConfig.mcpDirectTools ? { mcpDirectTools: [...recoveryAgentConfig.mcpDirectTools] } : {}),
1729
+ ...(recoveryAgentConfig.mutationTools ? { mutationTools: [...recoveryAgentConfig.mutationTools] } : {}),
1721
1730
  ...(recoveryAgentConfig.systemPrompt ? { systemPrompt: recoveryAgentConfig.systemPrompt } : {}),
1722
1731
  systemPromptMode: recoveryAgentConfig.systemPromptMode,
1723
1732
  inheritProjectContext: recoveryAgentConfig.inheritProjectContext,
1733
+ inheritGlobalContext: recoveryAgentConfig.inheritGlobalContext,
1724
1734
  inheritSkills: recoveryAgentConfig.inheritSkills,
1725
1735
  ...(resolvedSkills.length ? { skills: resolvedSkills.map((skill) => skill.name) } : {}),
1726
1736
  ...(recoveryAgentConfig.skillPath ? { skillPath: [...recoveryAgentConfig.skillPath] } : {}),
@@ -1770,7 +1780,9 @@ export function executeAsyncSingle(
1770
1780
  ...(params.externalJobFollowUp ? { externalJobFollowUp: params.externalJobFollowUp } : {}),
1771
1781
  ...(params.context ? { context: params.context } : {}),
1772
1782
  cwd: runnerCwd,
1783
+ requestedCwd: params.requestedCwd ?? runnerCwd,
1773
1784
  model,
1785
+ ...(contextLimit !== undefined ? { contextLimit } : {}),
1774
1786
  ...(params.fast ?? agentConfig.fast ? { fast: params.fast ?? agentConfig.fast } : {}),
1775
1787
  thinking: resolveEffectiveThinking(model, effectiveThinking),
1776
1788
  ...(thinkingCeiling ? { thinkingCeiling } : {}),
@@ -1781,10 +1793,14 @@ export function executeAsyncSingle(
1781
1793
  extensions: agentConfig.extensions,
1782
1794
  subagentOnlyExtensions: agentConfig.subagentOnlyExtensions,
1783
1795
  mcpDirectTools: agentConfig.mcpDirectTools,
1796
+ ...(toolPlan.mcpConfig ? { mcpConfig: toolPlan.mcpConfig } : {}),
1797
+ ...(toolPlan.runtimeServerNames ? { runtimeServerNames: toolPlan.runtimeServerNames } : {}),
1798
+ mutationTools: agentConfig.mutationTools,
1784
1799
  completionGuard: agentConfig.completionGuard,
1785
1800
  systemPrompt,
1786
1801
  systemPromptMode: agentConfig.systemPromptMode,
1787
1802
  inheritProjectContext: agentConfig.inheritProjectContext,
1803
+ inheritGlobalContext: agentConfig.inheritGlobalContext,
1788
1804
  inheritSkills: agentConfig.inheritSkills,
1789
1805
  skills: resolvedSkills.map((r) => r.name),
1790
1806
  outputPath,
@@ -1861,10 +1877,11 @@ export function executeAsyncSingle(
1861
1877
  lastUpdate: initialStatusAt,
1862
1878
  currentStep: 0,
1863
1879
  chainStepCount: 1,
1864
- steps: [{ agent, status: "pending" }],
1880
+ steps: [{ agent, status: "pending", ...(model ? { model } : {}), ...(contextLimit !== undefined ? { contextLimit } : {}) }],
1865
1881
  },
1866
1882
  path.join(asyncDir, "status.json"),
1867
1883
  (proof) => emitProcessTerminalEvent(ctx, proof),
1884
+ params.requestedCwd ?? runnerCwd,
1868
1885
  );
1869
1886
  } catch (error) {
1870
1887
  params.activeAsyncCapacity?.rollback();
@@ -325,7 +325,7 @@ export function readAsyncRecoveryDescriptor(asyncDir: string | undefined): Steer
325
325
  const parsed = value as Record<string, unknown>;
326
326
  const allowedFields = new Set([
327
327
  "version", "launchContractDigest", "sourceRunId", "agentContract", "agent", "sessionFile", "cwd", "model", "modelProvider", "modelOverrideFromParent", "fallbackModels", "thinking", "thinkingCeiling", "tools", "extensions",
328
- "subagentOnlyExtensions", "mcpDirectTools", "systemPrompt", "systemPromptMode", "inheritProjectContext", "inheritSkills", "skills",
328
+ "subagentOnlyExtensions", "mcpDirectTools", "mutationTools", "systemPrompt", "systemPromptMode", "inheritProjectContext", "inheritGlobalContext", "inheritSkills", "skills",
329
329
  "skillPath", "agentFilePath", "completionGuard", "memory", "outputPath", "outputMode", "structuredOutputSchema", "acceptance", "sessionDir", "artifactConfig",
330
330
  "artifactsDir", "maxOutput", "controlConfig", "context", "intercomBridge", "absoluteDeadlineAt", "initialTurnBudget", "initialToolBudget", "maxSubagentDepth", "share", "capabilityCeiling",
331
331
  "launchResolvedExtensions", "runFanoutBudget",
@@ -359,8 +359,10 @@ export function readAsyncRecoveryDescriptor(asyncDir: string | undefined): Steer
359
359
  for (const field of ["inheritProjectContext", "inheritSkills", "share"] as const) {
360
360
  if (typeof parsed[field] !== "boolean") throw new Error(`Invalid async recovery descriptor '${descriptorPath}': ${field} must be a boolean.`);
361
361
  }
362
+ if (parsed.inheritGlobalContext === undefined) parsed.inheritGlobalContext = parsed.inheritProjectContext;
363
+ else if (typeof parsed.inheritGlobalContext !== "boolean") throw new Error(`Invalid async recovery descriptor '${descriptorPath}': inheritGlobalContext must be a boolean.`);
362
364
  if (!Number.isInteger(parsed.maxSubagentDepth) || (parsed.maxSubagentDepth as number) < 0) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': maxSubagentDepth must be a non-negative integer.`);
363
- for (const field of ["fallbackModels", "tools", "extensions", "subagentOnlyExtensions", "mcpDirectTools", "skills", "skillPath"] as const) {
365
+ for (const field of ["fallbackModels", "tools", "extensions", "subagentOnlyExtensions", "mcpDirectTools", "mutationTools", "skills", "skillPath"] as const) {
364
366
  const item = parsed[field];
365
367
  if (item !== undefined && (!Array.isArray(item) || item.some((entry) => typeof entry !== "string" || !entry.trim()))) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': ${field} must contain non-empty strings.`);
366
368
  }
@@ -592,9 +594,11 @@ export function applySteeringRecoveryAgentConfig(agentConfig: AgentConfig, descr
592
594
  extensions: descriptor.extensions ? [...descriptor.extensions] : undefined,
593
595
  subagentOnlyExtensions: descriptor.subagentOnlyExtensions ? [...descriptor.subagentOnlyExtensions] : undefined,
594
596
  mcpDirectTools: descriptor.mcpDirectTools ? [...descriptor.mcpDirectTools] : undefined,
597
+ mutationTools: descriptor.mutationTools ? [...descriptor.mutationTools] : undefined,
595
598
  systemPrompt: descriptor.systemPrompt ?? agentConfig.systemPrompt,
596
599
  systemPromptMode: descriptor.systemPromptMode,
597
600
  inheritProjectContext: descriptor.inheritProjectContext,
601
+ inheritGlobalContext: descriptor.inheritGlobalContext,
598
602
  inheritSkills: descriptor.inheritSkills,
599
603
  skills: descriptor.skills ? [...descriptor.skills] : undefined,
600
604
  skillPath: descriptor.skillPath ? [...descriptor.skillPath] : undefined,
@@ -48,6 +48,7 @@ interface AsyncRunStepSummary {
48
48
  totalCost?: CostSummary;
49
49
  skills?: string[];
50
50
  model?: string;
51
+ contextLimit?: number;
51
52
  thinking?: string;
52
53
  attemptedModels?: string[];
53
54
  sessionFile?: string;
@@ -287,6 +288,7 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
287
288
  ...(step.totalCost ? { totalCost: step.totalCost } : {}),
288
289
  ...(step.skills ? { skills: step.skills } : {}),
289
290
  ...(step.model ? { model: step.model } : {}),
291
+ ...(step.contextLimit !== undefined ? { contextLimit: step.contextLimit } : {}),
290
292
  ...(step.thinking ? { thinking: step.thinking } : {}),
291
293
  ...(step.thinkingCeiling ? { thinkingCeiling: step.thinkingCeiling } : {}),
292
294
  ...(step.attemptedModels ? { attemptedModels: step.attemptedModels } : {}),
@@ -172,6 +172,7 @@ function statusStepForTask(task: RunnerSubagentStep): StatusStep {
172
172
  ...(task.sessionFile ? { sessionFile: task.sessionFile } : {}),
173
173
  skills: task.skills,
174
174
  model: task.model,
175
+ ...(task.contextLimit !== undefined ? { contextLimit: task.contextLimit } : {}),
175
176
  thinking: task.thinking,
176
177
  attemptedModels: task.modelCandidates && task.modelCandidates.length > 0 ? task.modelCandidates : task.model ? [task.model] : undefined,
177
178
  recentTools: [],
@@ -189,6 +190,7 @@ function statusStepsForRunnerStep(step: RunnerStep, stepIndex: number): StatusSt
189
190
  label: step.label ?? step.parallel.label ?? `Dynamic fanout (${step.collect.as})`,
190
191
  outputName: step.collect.as,
191
192
  structured: Boolean(step.collect.outputSchema),
193
+ ...(step.parallel.contextLimit !== undefined ? { contextLimit: step.parallel.contextLimit } : {}),
192
194
  status: "pending",
193
195
  recentTools: [],
194
196
  recentOutput: [],
@@ -16,6 +16,7 @@ import {
16
16
  } from "./completion-batcher.ts";
17
17
  import { SUBAGENT_ASYNC_COMPLETE_EVENT, SUBAGENT_FOREGROUND_COMPLETE_EVENT, type ParallelHandoffReference, type ScheduleOrigin, type SubagentState } from "../../shared/types.ts";
18
18
  import { isUnexplainedProcessSignal } from "../shared/process-signal.ts";
19
+ import type { ResultDeliveryOwnership } from "./result-delivery-ownership.ts";
19
20
 
20
21
  export interface SubagentNotifyDetails {
21
22
  agent: string;
@@ -91,6 +92,7 @@ export interface RegisterSubagentNotifyOptions {
91
92
  batchConfig?: CompletionBatchConfig;
92
93
  timers?: NotifyTimerApi;
93
94
  now?: () => number;
95
+ ownership?: Pick<ResultDeliveryOwnership, "owns">;
94
96
  }
95
97
 
96
98
  export interface CompletionNotifier {
@@ -228,6 +230,8 @@ export function formatGroupedCompletion(details: SubagentNotifyDetails[]): strin
228
230
  interface PendingCompletion {
229
231
  key: string;
230
232
  details: SubagentNotifyDetails;
233
+ sessionId: string;
234
+ completionOwnerId: unknown;
231
235
  triggerTurn: boolean;
232
236
  resolve(accepted: boolean): void;
233
237
  }
@@ -335,6 +339,10 @@ export default function registerSubagentNotify(
335
339
  const batchConfig = resolveCompletionBatchConfig(options.batchConfig);
336
340
  const batchers = new Map<string, CompletionBatcher<PendingCompletion>>();
337
341
  let disposed = false;
342
+ const ownsResult = options.ownership?.owns
343
+ ?? ((sessionId: string, completionOwnerId: unknown) => sessionId === state.currentSessionId
344
+ && typeof completionOwnerId === "string"
345
+ && completionOwnerId === state.completionOwnerId);
338
346
 
339
347
  const settle = (items: PendingCompletion[], accepted: boolean) => {
340
348
  for (const item of items) {
@@ -343,7 +351,18 @@ export default function registerSubagentNotify(
343
351
  item.resolve(accepted);
344
352
  }
345
353
  };
346
- const emit = (items: PendingCompletion[]) => settle(items, sendCompletion(pi, items));
354
+ const emit = (items: PendingCompletion[]) => {
355
+ const accepted: PendingCompletion[] = [];
356
+ const rejected: PendingCompletion[] = [];
357
+ for (const item of items) {
358
+ const owned = item.details.source === "foreground"
359
+ ? item.sessionId === state.currentSessionId
360
+ : ownsResult(item.sessionId, item.completionOwnerId);
361
+ (owned ? accepted : rejected).push(item);
362
+ }
363
+ settle(rejected, false);
364
+ settle(accepted, sendCompletion(pi, accepted));
365
+ };
347
366
  const getBatcher = (result: CompletionNotification) => {
348
367
  const key = completionBatchKey(result);
349
368
  let batcher = batchers.get(key);
@@ -360,8 +379,10 @@ export default function registerSubagentNotify(
360
379
  };
361
380
 
362
381
  const deliver = (result: CompletionNotification): Promise<boolean> => {
363
- if (disposed || typeof result.sessionId !== "string" || result.sessionId !== state.currentSessionId) return Promise.resolve(false);
364
- if (result.source !== "foreground" && (!state.completionOwnerId || result.completionOwnerId !== state.completionOwnerId)) return Promise.resolve(false);
382
+ if (disposed || typeof result.sessionId !== "string") return Promise.resolve(false);
383
+ if (result.source === "foreground") {
384
+ if (result.sessionId !== state.currentSessionId) return Promise.resolve(false);
385
+ } else if (!ownsResult(result.sessionId, result.completionOwnerId)) return Promise.resolve(false);
365
386
  if (result.intercomDelivered === true) return Promise.resolve(true);
366
387
  const key = buildCompletionKey(result, "notify");
367
388
  const seenAt = seen.get(key);
@@ -376,6 +397,8 @@ export default function registerSubagentNotify(
376
397
  const item: PendingCompletion = {
377
398
  key,
378
399
  details,
400
+ sessionId: result.sessionId,
401
+ completionOwnerId: result.completionOwnerId,
379
402
  triggerTurn: result.triggerTurn !== false,
380
403
  resolve,
381
404
  };
@@ -0,0 +1,45 @@
1
+ import type { SubagentState } from "../../shared/types.ts";
2
+
3
+ const MAX_CLAIMED_PREDECESSOR_SESSIONS = 8;
4
+
5
+ type ResultDeliveryState = Pick<SubagentState, "currentSessionId" | "completionOwnerId">;
6
+
7
+ export interface ResultDeliveryOwnership {
8
+ owns(sessionId: string, completionOwnerId: unknown): boolean;
9
+ claimedSessionIds(): readonly string[];
10
+ claimPredecessor(previousSessionFile: string | undefined, previousRuntimeSessionId: string | null): boolean;
11
+ clear(): void;
12
+ }
13
+
14
+ export function createResultDeliveryOwnership(state: ResultDeliveryState): ResultDeliveryOwnership {
15
+ const claimed = new Map<string, string>();
16
+
17
+ const currentOwner = (): string | undefined => typeof state.completionOwnerId === "string" && state.completionOwnerId
18
+ ? state.completionOwnerId
19
+ : undefined;
20
+
21
+ return {
22
+ owns(sessionId, completionOwnerId) {
23
+ const owner = currentOwner();
24
+ if (!owner || completionOwnerId !== owner) return false;
25
+ return sessionId === state.currentSessionId || claimed.get(sessionId) === owner;
26
+ },
27
+ claimedSessionIds() {
28
+ const owner = currentOwner();
29
+ if (!owner) return [];
30
+ return [...claimed].flatMap(([sessionId, claimedOwner]) => claimedOwner === owner ? [sessionId] : []);
31
+ },
32
+ claimPredecessor(previousSessionFile, previousRuntimeSessionId) {
33
+ const owner = currentOwner();
34
+ if (!owner || !previousSessionFile || !previousRuntimeSessionId) return false;
35
+ if (previousSessionFile !== previousRuntimeSessionId || state.currentSessionId !== previousRuntimeSessionId) return false;
36
+ claimed.delete(previousRuntimeSessionId);
37
+ claimed.set(previousRuntimeSessionId, owner);
38
+ while (claimed.size > MAX_CLAIMED_PREDECESSOR_SESSIONS) claimed.delete(claimed.keys().next().value!);
39
+ return true;
40
+ },
41
+ clear() {
42
+ claimed.clear();
43
+ },
44
+ };
45
+ }
@@ -25,6 +25,7 @@ import { recordWaitCompletion } from "./wait-completions.ts";
25
25
  import { MISSION_BINDING_FILE, syncMissionFromAsyncCompletion } from "../../missions/lifecycle.ts";
26
26
  import { missionObserverResultCandidateFiles, promotePendingResultFile, removeMissionObserverIndex, removeResultIndex, resultCandidateFilesForSession, resultPayloadPathForIndexedRun, resultPayloadPathForMissionObserverRun, resultPayloadPathForSessionRun, writeAsyncResultFile, writeResultIndexForData } from "./result-files.ts";
27
27
  import type { CompletionNotifier, CompletionNotification } from "./notify.ts";
28
+ import type { ResultDeliveryOwnership } from "./result-delivery-ownership.ts";
28
29
 
29
30
  const WATCHER_RESTART_DELAY_MS = 3000;
30
31
  const POLL_INTERVAL_MS = 3000;
@@ -60,6 +61,8 @@ type ResultWatcherDeps = {
60
61
  /** Control how slow result-index scans are logged. Defaults to \"activity\". */
61
62
  resultScanLogging?: "all" | "activity" | "off";
62
63
  platform?: NodeJS.Platform;
64
+ /** Shared current/predecessor session ownership used by the notifier. */
65
+ ownership?: Pick<ResultDeliveryOwnership, "owns" | "claimedSessionIds">;
63
66
  };
64
67
 
65
68
  type ResultFileChild = {
@@ -200,6 +203,7 @@ export function createResultWatcher(
200
203
  deps: ResultWatcherDeps = {},
201
204
  ): {
202
205
  startResultWatcher: () => void;
206
+ transitionResultDelivery: () => void;
203
207
  primeExistingResults: (options?: { triggerTurn?: boolean }) => void;
204
208
  refreshResultDelivery: () => void;
205
209
  stopResultWatcher: () => void;
@@ -218,14 +222,16 @@ export function createResultWatcher(
218
222
  // The sole in-memory ownership lease. It is acquired for one active session
219
223
  // and revoked before the watcher, queues, or callbacks are torn down.
220
224
  let activeSessionId: string | null = null;
225
+ const ownsResult = deps.ownership?.owns
226
+ ?? ((sessionId: string, completionOwnerId: unknown) => sessionId === state.currentSessionId
227
+ && typeof completionOwnerId === "string"
228
+ && completionOwnerId === state.completionOwnerId);
229
+ const claimedSessionIds = () => deps.ownership?.claimedSessionIds() ?? [];
221
230
 
222
231
  const ownsCompletion = (sessionId: string, completionOwnerId: unknown, epoch: number) => {
223
232
  if (!deliveryActive || epoch !== deliveryEpoch) return false;
224
233
  if (!activeSessionId && state.currentSessionId) activeSessionId = state.currentSessionId;
225
- return activeSessionId === sessionId
226
- && state.currentSessionId === sessionId
227
- && typeof completionOwnerId === "string"
228
- && completionOwnerId === state.completionOwnerId;
234
+ return activeSessionId === state.currentSessionId && ownsResult(sessionId, completionOwnerId);
229
235
  };
230
236
 
231
237
  const scheduleResult = (file: string, triggerTurn: boolean, delayMs = 0) => {
@@ -246,8 +252,11 @@ export function createResultWatcher(
246
252
  const resultPayloadPath = (file: string, observed?: ReadonlySet<string>): string | undefined => {
247
253
  if (file !== path.basename(file) || !file.endsWith(".json")) return undefined;
248
254
  const runId = file.replace(/\.json$/i, "");
249
- const sessionResult = state.currentSessionId ? resultPayloadPathForSessionRun(resultsDir, state.currentSessionId, runId) : undefined;
250
- if (sessionResult) return sessionResult;
255
+ for (const sessionId of [state.currentSessionId, ...claimedSessionIds()]) {
256
+ if (!sessionId) continue;
257
+ const sessionResult = resultPayloadPathForSessionRun(resultsDir, sessionId, runId);
258
+ if (sessionResult) return sessionResult;
259
+ }
251
260
  const observerResult = resultPayloadPathForMissionObserverRun(resultsDir, runId);
252
261
  if (observerResult) return observerResult;
253
262
  if (observed?.has(runId)) {
@@ -308,7 +317,7 @@ export function createResultWatcher(
308
317
  // Missing identity stays on the normal parser path so malformed or legacy
309
318
  // files keep their existing diagnostics and compatibility behavior.
310
319
  if (!identity.sessionId) return true;
311
- if (identity.sessionId === state.currentSessionId && identity.completionOwnerId === state.completionOwnerId) return true;
320
+ if (ownsResult(identity.sessionId, identity.completionOwnerId)) return true;
312
321
  if (identity.asyncDir && fsApi.existsSync(path.join(identity.asyncDir, MISSION_BINDING_FILE))) return true;
313
322
  if (identity.runId && (observed ?? observedRunIds()).has(identity.runId)) return true;
314
323
  return Boolean(deps.observeCompletion && !deps.observedCompletionRunIds);
@@ -621,8 +630,9 @@ export function createResultWatcher(
621
630
  };
622
631
  const indexedResultCandidates = (observed: ReadonlySet<string>): string[] => {
623
632
  const files = new Set<string>();
624
- if (state.currentSessionId) {
625
- for (const file of resultCandidateFilesForSession(resultsDir, state.currentSessionId)) files.add(file);
633
+ for (const sessionId of [state.currentSessionId, ...claimedSessionIds()]) {
634
+ if (!sessionId) continue;
635
+ for (const file of resultCandidateFilesForSession(resultsDir, sessionId)) files.add(file);
626
636
  }
627
637
  for (const runId of state.asyncJobs.keys()) files.add(`${runId}.json`);
628
638
  for (const file of missionObserverResultCandidateFiles(resultsDir)) files.add(file);
@@ -755,6 +765,13 @@ export function createResultWatcher(
755
765
  }
756
766
  };
757
767
 
768
+ const transitionResultDelivery = () => {
769
+ deliveryActive = true;
770
+ activeSessionId = state.currentSessionId;
771
+ deliveryEpoch += 1;
772
+ identityCache.clear();
773
+ };
774
+
758
775
  const stopResultWatcher = () => {
759
776
  deliveryActive = false;
760
777
  activeSessionId = null;
@@ -769,5 +786,5 @@ export function createResultWatcher(
769
786
  identityCache.clear();
770
787
  };
771
788
 
772
- return { startResultWatcher, primeExistingResults, stopResultWatcher, refreshResultDelivery: () => { primeExistingResults(); startDemandPolling(); } };
789
+ return { startResultWatcher, transitionResultDelivery, primeExistingResults, stopResultWatcher, refreshResultDelivery: () => { primeExistingResults(); startDemandPolling(); } };
773
790
  }