pi-subagents 0.60.0 → 0.62.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.
- package/CHANGELOG.md +55 -0
- package/docs/agents.md +7 -3
- package/docs/configuration.md +9 -5
- package/docs/extension-api.md +14 -7
- package/docs/models.md +1 -1
- package/docs/observability.md +1 -1
- package/docs/tool-reference.md +13 -4
- package/docs/workflows.md +14 -13
- package/install.mjs +1 -1
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +6 -4
- package/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
- package/skills/pi-subagents/references/execution-controls.md +41 -11
- package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +4 -4
- package/skills/pi-subagents/references/review-and-validation.md +1 -1
- package/src/agents/agent-management.ts +121 -63
- package/src/agents/agent-serializer.ts +3 -0
- package/src/agents/agents.ts +543 -223
- package/src/agents/runtime-agent-registry.ts +5 -1
- package/src/api/background-work.ts +7 -2
- package/src/api/external-runs.ts +67 -4
- package/src/api/preflight.ts +17 -8
- package/src/api/shared-types.ts +1 -0
- package/src/extension/index.ts +7 -4
- package/src/extension/public-execution.ts +48 -4
- package/src/extension/rpc.ts +62 -4
- package/src/extension/schemas.ts +17 -9
- package/src/extension/tool-description.ts +15 -19
- package/src/runs/background/active-async-capacity.ts +26 -7
- package/src/runs/background/async-execution.ts +98 -51
- package/src/runs/background/async-job-tracker.ts +62 -3
- package/src/runs/background/async-resume.ts +6 -3
- package/src/runs/background/async-status.ts +59 -9
- package/src/runs/background/auto-drain.ts +1 -1
- package/src/runs/background/fleet-view.ts +1 -1
- package/src/runs/background/process-terminal.ts +16 -0
- package/src/runs/background/result-watcher.ts +1 -1
- package/src/runs/background/resume-guidance.ts +1 -1
- package/src/runs/background/run-status.ts +2 -2
- package/src/runs/background/scheduled-runs.ts +63 -6
- package/src/runs/background/steering.ts +4 -1
- package/src/runs/background/subagent-runner.ts +15 -9
- package/src/runs/background/subagent-wait.ts +20 -21
- package/src/runs/background/wait-completions.ts +1 -1
- package/src/runs/background/wait-tool.ts +18 -18
- package/src/runs/foreground/execution.ts +75 -9
- package/src/runs/foreground/subagent-executor.ts +181 -60
- package/src/runs/shared/acceptance.ts +113 -27
- package/src/runs/shared/capability-ceiling.ts +1 -0
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/host-step-status.ts +1 -0
- package/src/runs/shared/model-fallback.ts +61 -17
- package/src/runs/shared/parallel-utils.ts +2 -6
- package/src/runs/shared/permissions.ts +1 -1
- package/src/runs/shared/pi-args.ts +24 -12
- package/src/runs/shared/pi-spawn.ts +69 -35
- package/src/runs/shared/structured-output.ts +33 -6
- package/src/runs/shared/subagent-prompt-runtime.ts +20 -3
- package/src/runs/shared/task-intent.ts +17 -6
- package/src/runs/shared/tool-timeout.ts +1 -1
- package/src/runs/shared/workflow-graph.ts +3 -2
- package/src/shared/atomic-json.ts +3 -1
- package/src/shared/fork-context.ts +0 -12
- package/src/shared/fork-session-cwd.ts +27 -0
- package/src/shared/launch-contract.ts +3 -0
- package/src/shared/types.ts +43 -4
- package/src/shared/workflow-child-permit.ts +91 -0
- package/src/slash/prompt-template-bridge.ts +37 -1
- package/src/slash/slash-commands.ts +18 -26
- package/src/slash/subagents-admin.ts +2 -0
- package/src/tui/render.ts +95 -52
- package/src/workflows/scripted-workflow.ts +153 -4
- package/src/workflows/workflow-child-summary.ts +1 -1
- package/src/workflows/workflow-receipt.ts +41 -4
- package/src/workflows/workflow-resources.ts +150 -0
|
@@ -10,33 +10,25 @@ const WORKFLOW_RESUME_KEY_GUIDANCE = "Each workflow key identifies one result la
|
|
|
10
10
|
const WORKFLOW_OUTPUT_BINDING_GUIDANCE = "For durable workflow child files, set output on runs.run/runs.all; task filename prose is not an output declaration, and return the child's outputReference, outputPathMapping, or artifactPaths instead of inventing a literal path.";
|
|
11
11
|
const WORKFLOW_LANES_GUIDANCE = "For bounded parallel sequential chains, use runs.lanes([{key,stages:[{key,agent,task},{key,resume:'previous',task},...]}]); first stages run together, later stages sequence per lane, and the bounded board reports lane-local failures. Only an explicit structuredOutput.verdict === 'blocked' blocks a successful stage; reviewer prose is not parsed.";
|
|
12
12
|
const WORKFLOW_SCRIPT_PORTABILITY_GUIDANCE = "workflowScript rejects nested async function, arrow, and method helpers; use top-level await, plain helper functions that return runs.run(...), or explicit Promise chains instead.";
|
|
13
|
-
const
|
|
14
|
-
const
|
|
13
|
+
const WORKFLOW_RESOURCE_GUIDANCE = "For permission/policy-extension interoperability, use an extension-owned named resource such as {workflow:'review',args:{task:'...'}} or {workflow:'run-ci',args:{command:'npm test'}}. The host resolves the script and authority internally so policy can distinguish it from raw workflowScript/workflowScriptPath; args are bounded plain data, and do not combine workflow with agent, task, workflowScript, or workflowScriptPath.";
|
|
14
|
+
const WORKFLOW_HOST_GUIDANCE = "For permission-sensitive host calls, use an extension-owned resource such as {workflow:'run-ci',args:{command:'npm test'}}; raw workflowScript/workflowScriptPath have unknown resource provenance and cannot use runs.host. In a resource that grants it, await runs.host(key,{kind:'command',command,timeoutMs,output?,role?,provider?}). runs.host has no per-step cwd: commands and relative output paths use the workflow cwd; set cwd on the outer subagent request instead (for example, {cwd:'/path/to/worktree',workflowScript:'...'}), or put a trusted directory change in the command (for example, 'cd /path/to/worktree && npm test'). v1 supports only command steps; output is bounded and command failure fails the workflow.";
|
|
15
15
|
|
|
16
|
-
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,
|
|
16
|
+
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, workflowScriptPath to load a script from the request cwd, or a named workflow resource for permission/policy-aware execution. ${WORKFLOW_RESOURCE_GUIDANCE} 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. ${WORKFLOW_SCRIPT_PORTABILITY_GUIDANCE} ${WORKFLOW_LANES_GUIDANCE} ${WORKFLOW_HOST_GUIDANCE} ${EXTERNAL_CLI_RUNNER_GUIDANCE} Use action only for management/control. Use guide or the pi-subagents skill for advanced workflow details.`;
|
|
17
17
|
|
|
18
18
|
export const SUBAGENT_TOOL_PROMPT_SNIPPET = "Delegate to subagents; orchestrate in one workflowScript call.";
|
|
19
19
|
|
|
20
20
|
export const SUBAGENT_TOOL_PROMPT_GUIDELINES = [
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"workflowScript
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
WORKFLOW_HOST_GUIDANCE,
|
|
27
|
-
WORKFLOW_RESUME_KEY_GUIDANCE,
|
|
28
|
-
WORKFLOW_OUTPUT_BINDING_GUIDANCE,
|
|
29
|
-
"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.",
|
|
30
|
-
"Keep one writer per cwd/worktree unless writers run in isolated worktrees.",
|
|
31
|
-
"To pass an explicit model to a child, first call { action: \"models\" } and copy an exact provider/id (e.g. provider/model-id); 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. provider/model-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.",
|
|
32
|
-
EXTERNAL_CLI_RUNNER_GUIDANCE,
|
|
33
|
-
"Use guide or the pi-subagents skill for advanced scheduling, missions, steering, and retention.",
|
|
21
|
+
'Use subagent only when delegation is needed. Before execution, call { action: "list" } and run only executable, non-disabled agents.',
|
|
22
|
+
'Omit action for execution; use { agent, task? } for one child. For multi-step or parallel work, make exactly one top-level { workflowScript, async: true } call and launch children only inside it. Use action only for management/control.',
|
|
23
|
+
"workflowScript rejects nested async function, arrow, and method helpers; use top-level await, plain helper functions, or explicit Promise chains.",
|
|
24
|
+
"Inside workflowScript, use runs.run/runs.all and await their results. runs.all returns an ordered array, not a key map; stored runs.run promises must later be observed with direct await, Promise.race, or Promise.all.",
|
|
25
|
+
'Keep one writer per cwd/worktree; isolate concurrent writers. For durable files, set output on runs.run/runs.all and return the child\'s outputReference, outputPathMapping, or artifactPaths. For advanced workflows, read the bundled pi-subagents skill or call { action: "guide", topic: "workflows" }.',
|
|
34
26
|
];
|
|
35
27
|
|
|
36
28
|
export const SUBAGENT_SAFETY_GUIDANCE = `SAFETY-CRITICAL SUBAGENT GUIDANCE:
|
|
37
29
|
• Use { action: "list" } before execution and only run executable/non-disabled agents.
|
|
38
30
|
• Keep execution and management separate: omit action for structured single-child or workflowScript execution; use action only for management/control.
|
|
39
|
-
• Async/background runs are the normal default unless config sets asyncByDefault:false; set async:true explicitly when async behavior matters. Use async:false only when the parent must block until completion. Async mode still shows progress. Final reviews and gate checks stay async; needing a result is not a blocking reason. After an async launch, continue independent work only until its next dependency barrier; consume the result before work that depends on it. Do not sleep or poll status just to wait; use
|
|
31
|
+
• Async/background runs are the normal default unless config sets asyncByDefault:false; set async:true explicitly when async behavior matters. Use async:false only when the parent must block until completion. Async mode still shows progress. Final reviews and gate checks stay async; needing a result is not a blocking reason. After an async launch, continue independent work only until its next dependency barrier; consume the result before work that depends on it. Ordinary async subagents notify this session natively, so return control and do not call bg_wait merely to get a completion wake. Do not sleep or poll status just to wait; use bg_wait only for provider, detached, or other background work without a native notification when this turn must receive its result.
|
|
40
32
|
• ${WORKFLOW_RESUME_KEY_GUIDANCE}
|
|
41
33
|
• ${WORKFLOW_OUTPUT_BINDING_GUIDANCE}
|
|
42
34
|
• ${WORKFLOW_HOST_GUIDANCE}
|
|
@@ -47,6 +39,8 @@ export const SUBAGENT_SAFETY_GUIDANCE = `SAFETY-CRITICAL SUBAGENT GUIDANCE:
|
|
|
47
39
|
|
|
48
40
|
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.
|
|
49
41
|
|
|
42
|
+
${WORKFLOW_RESOURCE_GUIDANCE}
|
|
43
|
+
|
|
50
44
|
EXECUTION:
|
|
51
45
|
• ${EXTERNAL_CLI_RUNNER_GUIDANCE}
|
|
52
46
|
• Before executing, use { action: "list" } and run only executable/non-disabled configured agents.
|
|
@@ -63,12 +57,14 @@ EXECUTION:
|
|
|
63
57
|
MANAGEMENT / CONTROL (use action; omit execution fields):
|
|
64
58
|
• validate checks workflowScript or workflowScriptPath syntax and statically decidable structure without launching children. list, get, models, guide, children.list, create, update, delete, eject, disable, enable, reset, status, debug.run, doctor, grant-spawn-budget, worktree.discard, worktree.cleanup (plan-only), lane.status, lane.recordMerge, lane.recordSupersession, refine/refine.show/refine.rollback, mission.create/list/show/update/resolve-decision/attach-run/close, inspector.open/status/close, project.open/status/close, and watchdog actions remain available. Use {action:"guide", topic:"overview"} for packaged current-version help; topics are overview, workflows, agents, missions, observability, tool-reference, configuration, models, watchdog, and extension-api.
|
|
65
59
|
• status, interrupt, stop, resume, and steer manage live or persisted runs. Use status view:"fleet" for an overview or view:"transcript" with id and optional index to tail output.
|
|
66
|
-
• Create durable project schedules with { action:"schedule.create", id?, name?, at:"+10m" | ISO, workflowScript:"return runs.run('main', {agent:'worker', task:'...'})" }, or use workflowScriptPath instead. Manage them with schedule.list/show/history/pause/resume/run/run-due/delete. This first slice supports fixed intervals; calendar schedules and schedule mission attachment are deferred.
|
|
60
|
+
• Create durable project schedules with { action:"schedule.create", id?, name?, sessionOnly?:true, at:"+10m" | ISO, workflowScript:"return runs.run('main', {agent:'worker', task:'...'})" }, or use workflowScriptPath instead. With sessionOnly:true, the schedule records the creating session file and only that session can restore or execute it; omitted/false preserves project-wide behavior. Manage them with schedule.list/show/history/pause/resume/run/run-due/delete. This first slice supports fixed intervals; calendar schedules and schedule mission attachment are deferred.
|
|
67
61
|
|
|
68
62
|
${SUBAGENT_SAFETY_GUIDANCE}`;
|
|
69
63
|
|
|
70
64
|
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.
|
|
71
65
|
|
|
66
|
+
${WORKFLOW_RESOURCE_GUIDANCE}
|
|
67
|
+
|
|
72
68
|
EXECUTE:
|
|
73
69
|
• ${EXTERNAL_CLI_RUNNER_GUIDANCE}
|
|
74
70
|
• Call { action:"list" } first and use only executable/non-disabled agents.
|
|
@@ -85,7 +81,7 @@ MANAGE / CONTROL:
|
|
|
85
81
|
• A mission object needs exactly one non-empty title or summary; objective and labels are optional. goal may only be true and requires budget:{tokens}.
|
|
86
82
|
|
|
87
83
|
ASYNC / SAFETY:
|
|
88
|
-
• Omitted async follows asyncByDefault config; set async:true explicitly when async behavior matters. Continue independent work only until its next dependency barrier; consume the result before work that depends on it. Do not sleep or poll merely to wait; use
|
|
84
|
+
• Omitted async follows asyncByDefault config; set async:true explicitly when async behavior matters. Continue independent work only until its next dependency barrier; consume the result before work that depends on it. Ordinary async subagents notify this session natively, so return control and do not call bg_wait merely to get a completion wake. Do not sleep or poll merely to wait; use bg_wait only for provider, detached, or other background work without a native notification when this turn must receive its result.
|
|
89
85
|
• ${WORKFLOW_RESUME_KEY_GUIDANCE}
|
|
90
86
|
• ${WORKFLOW_OUTPUT_BINDING_GUIDANCE}
|
|
91
87
|
• ${WORKFLOW_HOST_GUIDANCE}
|
|
@@ -33,6 +33,7 @@ export interface ActiveAsyncCapacityHandle {
|
|
|
33
33
|
markStarted(runnerProcessInstanceId: string): void;
|
|
34
34
|
markWorkflowStarted(): void;
|
|
35
35
|
rollback(): boolean;
|
|
36
|
+
rollbackBeforeRunnerProceed(runnerProcessInstanceId: string): boolean;
|
|
36
37
|
reconcile(liveWorkflowRunIds?: ReadonlySet<string>): ActiveAsyncCapacitySnapshot;
|
|
37
38
|
}
|
|
38
39
|
|
|
@@ -43,6 +44,7 @@ interface CapacityOptions {
|
|
|
43
44
|
abandonedSlotReleaseAfterMs?: number | false;
|
|
44
45
|
pidLiveness?: (pid: number) => PidLiveness;
|
|
45
46
|
afterSlotRename?: (releasedDir: string) => void;
|
|
47
|
+
writeOwner?: (filePath: string, owner: ActiveAsyncCapacityOwnerV1) => void;
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
export interface ActiveAsyncCapacityReleaseEvidence {
|
|
@@ -380,6 +382,7 @@ function createSlot(poolDir: string, owner: ActiveAsyncCapacityOwnerV1): boolean
|
|
|
380
382
|
|
|
381
383
|
function handleFor(owner: ActiveAsyncCapacityOwnerV1, limit: number, options: CapacityOptions, rollbackOwner?: ActiveAsyncCapacityOwnerV1): ActiveAsyncCapacityHandle {
|
|
382
384
|
const rootDir = options.rootDir ?? ACTIVE_ASYNC_CAPACITY_DIR;
|
|
385
|
+
const writeOwner = options.writeOwner ?? writePrivateAtomicJson;
|
|
383
386
|
const dir = slotDir(sessionDir(owner.ownerSessionId, rootDir), owner.slot);
|
|
384
387
|
return {
|
|
385
388
|
owner,
|
|
@@ -388,14 +391,10 @@ function handleFor(owner: ActiveAsyncCapacityOwnerV1, limit: number, options: Ca
|
|
|
388
391
|
const current = matchingOwner(dir, owner);
|
|
389
392
|
if (!current) return false;
|
|
390
393
|
const next = { ...current, runnerProcessInstanceId, runnerStartedAt: options.now?.() ?? Date.now() };
|
|
391
|
-
// Mark memory first
|
|
392
|
-
//
|
|
394
|
+
// Mark memory first so pre-proceed cleanup can distinguish a failed durable
|
|
395
|
+
// bind from an unrelated unstarted reservation.
|
|
393
396
|
Object.assign(owner, next);
|
|
394
|
-
|
|
395
|
-
writePrivateAtomicJson(path.join(dir, "owner.json"), next);
|
|
396
|
-
} catch (error) {
|
|
397
|
-
console.error(`Failed to bind active async capacity to runner '${runnerProcessInstanceId}'; capacity will remain occupied:`, error);
|
|
398
|
-
}
|
|
397
|
+
writeOwner(path.join(dir, "owner.json"), next);
|
|
399
398
|
return true;
|
|
400
399
|
});
|
|
401
400
|
if (!claimed.acquired || !claimed.value) throw new Error(`Active async capacity ownership changed for run '${owner.runId}'.`);
|
|
@@ -427,6 +426,26 @@ function handleFor(owner: ActiveAsyncCapacityOwnerV1, limit: number, options: Ca
|
|
|
427
426
|
});
|
|
428
427
|
return claimed.acquired && claimed.value;
|
|
429
428
|
},
|
|
429
|
+
rollbackBeforeRunnerProceed(runnerProcessInstanceId) {
|
|
430
|
+
const claimed = withSlotClaim(dir, () => {
|
|
431
|
+
const current = matchingOwner(dir, owner);
|
|
432
|
+
if (!current) return false;
|
|
433
|
+
const boundToRunner = current.runnerProcessInstanceId === runnerProcessInstanceId;
|
|
434
|
+
const bindingFailedBeforeProceed = current.runnerProcessInstanceId === undefined && current.runnerStartedAt === undefined && owner.runnerProcessInstanceId === runnerProcessInstanceId;
|
|
435
|
+
if (!boundToRunner && !bindingFailedBeforeProceed) return false;
|
|
436
|
+
if (rollbackOwner) {
|
|
437
|
+
writePrivateAtomicJson(path.join(dir, "owner.json"), rollbackOwner);
|
|
438
|
+
Object.assign(owner, rollbackOwner);
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
const releasedDir = path.join(path.dirname(dir), `.${path.basename(dir)}.released-${randomUUID()}`);
|
|
442
|
+
fs.renameSync(dir, releasedDir);
|
|
443
|
+
options.afterSlotRename?.(releasedDir);
|
|
444
|
+
fs.rmSync(releasedDir, { recursive: true, force: true });
|
|
445
|
+
return true;
|
|
446
|
+
});
|
|
447
|
+
return claimed.acquired && claimed.value;
|
|
448
|
+
},
|
|
430
449
|
reconcile(liveWorkflowRunIds) {
|
|
431
450
|
return reconcileActiveAsyncCapacity(owner.ownerSessionId, limit, { ...options, rootDir, liveWorkflowRunIds });
|
|
432
451
|
},
|
|
@@ -11,7 +11,7 @@ import { createRequire } from "node:module";
|
|
|
11
11
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
12
12
|
import { discoverAgents, formatUnknownAgentError, unknownAgentDiagnosticContext, type AgentConfig, type UnknownAgentDiagnosticContext } from "../../agents/agents.ts";
|
|
13
13
|
import { appendAgentRefinementOverlay } from "../../agents/agent-refinements.ts";
|
|
14
|
-
import { writePrivateAtomicJson } from "../../shared/atomic-json.ts";
|
|
14
|
+
import { createAtomicJsonWriter, writePrivateAtomicJson } from "../../shared/atomic-json.ts";
|
|
15
15
|
import { currentCompletionOwnerId } from "../../shared/completion-owner.ts";
|
|
16
16
|
import { planChildLaunch, resolveStepBehavior, suppressProgressForReadOnlyTask, type ResolvedStepBehavior } from "../shared/child-launch-plan.ts";
|
|
17
17
|
import { applyThinkingSuffix, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan } from "../shared/pi-args.ts";
|
|
@@ -19,14 +19,14 @@ import { injectOutputPathSystemPrompt, injectSingleOutputInstruction, normalizeS
|
|
|
19
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
|
-
import { resolvePiPackageRoot } from "../shared/pi-spawn.ts";
|
|
22
|
+
import { resolveInstalledPiPackageRoot, resolvePiPackageRoot } from "../shared/pi-spawn.ts";
|
|
23
23
|
import { preflightLaunchCwd } from "../shared/launch-cwd.ts";
|
|
24
24
|
import { resolveNodeExecutable } from "../../shared/node-executable.ts";
|
|
25
25
|
import { backgroundProcessOptions } from "../shared/background-process-options.ts";
|
|
26
26
|
import { buildSkillInjection, normalizeSkillInput, resolveSkillsWithFallback } from "../../agents/skills.ts";
|
|
27
27
|
import { buildAgentMemoryInjection } from "../../agents/agent-memory.ts";
|
|
28
28
|
import { PI_CODING_AGENT_PACKAGE_ROOT_ENV, PROMPT_REDACTED, resolveChildCwd } from "../../shared/utils.ts";
|
|
29
|
-
import { buildModelCandidates,
|
|
29
|
+
import { buildModelCandidates, resolveEffectiveSubagentModel, resolveModelOrigin, resolveSubagentModelOverride, type AvailableModelInfo, type ModelOrigin, type ParentModel } from "../shared/model-fallback.ts";
|
|
30
30
|
import { resolveToolTimeoutMs, toolTimeoutFromEnv } from "../shared/tool-timeout.ts";
|
|
31
31
|
import { resolveModelScopesForAgent, type ModelScopeConfig } from "../shared/model-scope.ts";
|
|
32
32
|
import { findModelInfo, resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
@@ -35,7 +35,7 @@ import { resolveExpectedWorktreeAgentCwd } from "../shared/worktree.ts";
|
|
|
35
35
|
import { buildWorkflowGraphSnapshot } from "../shared/workflow-graph.ts";
|
|
36
36
|
import { ChainOutputValidationError, validateChainOutputBindings } from "../shared/chain-outputs.ts";
|
|
37
37
|
import { createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
38
|
-
import { resolveEffectiveAcceptance, validateAcceptanceInput, validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
38
|
+
import { resolveAcceptanceReportMode, resolveEffectiveAcceptance, validateAcceptanceInput, validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
39
39
|
import { createRunFanoutBudget, writeRunFanoutBudgetDescriptor } from "../shared/run-fanout-budget.ts";
|
|
40
40
|
import { validateImplementationToolContract } from "../shared/completion-guard.ts";
|
|
41
41
|
import {
|
|
@@ -70,7 +70,7 @@ import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
|
70
70
|
import { usageBudgetState } from "../shared/usage-budget.ts";
|
|
71
71
|
import type { ImportedAsyncRoot } from "./chain-root-attachment.ts";
|
|
72
72
|
import type { SessionLeaseRequest } from "../shared/session-lease.ts";
|
|
73
|
-
import { finalizeProcessTerminal, readProcessTerminal } from "./process-terminal.ts";
|
|
73
|
+
import { finalizeProcessTerminal, initializeProcessTerminal, readProcessTerminal } from "./process-terminal.ts";
|
|
74
74
|
import type { ActiveAsyncCapacityHandle } from "./active-async-capacity.ts";
|
|
75
75
|
import { statusStepDescription } from "./chain-append.ts";
|
|
76
76
|
import { SUBAGENT_PROCESS_TERMINAL_EVENT } from "../../shared/types.ts";
|
|
@@ -81,7 +81,7 @@ import { normalizeExtensionBindings, omitExtensionBindingsEnv, type ExtensionBin
|
|
|
81
81
|
import { assertWorkflowLaneKey, normalizeWorkflowLaneMetadata } from "../shared/lane-metadata.ts";
|
|
82
82
|
|
|
83
83
|
const require = createRequire(import.meta.url);
|
|
84
|
-
const piPackageRoot = resolvePiPackageRoot();
|
|
84
|
+
const piPackageRoot = resolvePiPackageRoot() ?? resolveInstalledPiPackageRoot();
|
|
85
85
|
|
|
86
86
|
function resolveJitiCliFromPackageJson(packageJsonPath: string): string | undefined {
|
|
87
87
|
if (!fs.existsSync(packageJsonPath)) return undefined;
|
|
@@ -235,6 +235,7 @@ interface AsyncSingleParams {
|
|
|
235
235
|
structuredOutputSchema?: JsonSchemaObject;
|
|
236
236
|
modelOverride?: string;
|
|
237
237
|
modelOverrideFromParent?: boolean;
|
|
238
|
+
modelOrigin?: ModelOrigin;
|
|
238
239
|
fast?: boolean;
|
|
239
240
|
thinkingOverride?: AgentConfig["thinking"];
|
|
240
241
|
availableModels?: AvailableModelInfo[];
|
|
@@ -339,15 +340,15 @@ export function formatAsyncStartedMessage(headline: string, interactive: boolean
|
|
|
339
340
|
const guidance = interactive
|
|
340
341
|
? [
|
|
341
342
|
"The async run is detached and running in the background.",
|
|
342
|
-
"You are in an interactive session.
|
|
343
|
-
"
|
|
344
|
-
"
|
|
343
|
+
"You are in an interactive session. Return control to the user now; Pi will wake you through the native completion notification when this subagent completes or needs attention. Do not run sleep/polling loops to wait for this async subagent; it does not need a wait call.",
|
|
344
|
+
"Use bg_wait only for provider, detached, or other background work that lacks a native completion notification.",
|
|
345
|
+
"If the current turn must receive results from work without a native notification before it ends, call blocking bg_wait(); ordinary async subagent runs do not need a wait call because their completion is delivered natively.",
|
|
345
346
|
"Otherwise, continue any independent work or return control to the user. Use subagent({ action: \"status\", id: \"...\" }) for a one-shot status/result or to inspect a blocked/stale run, never as a wait loop.",
|
|
346
347
|
]
|
|
347
348
|
: [
|
|
348
349
|
"The async run is detached. Do not run sleep timers or polling loops just to wait for it.",
|
|
349
|
-
"This is a non-interactive run: Pi auto-drains current-session
|
|
350
|
-
"Use subagent({ action: \"status\", id: \"...\" }) when you need a one-shot status/result or to inspect a blocked/stale run
|
|
350
|
+
"This is a non-interactive run: Pi auto-drains current-session subagent work at agent_end so detached children are not abandoned. Use bg_wait only when this turn must receive provider, detached, or other background-work results that have no native completion notification.",
|
|
351
|
+
"Use subagent({ action: \"status\", id: \"...\" }) when you need a one-shot status/result or to inspect a blocked/stale run; do not poll in a loop.",
|
|
351
352
|
];
|
|
352
353
|
return [headline, "", ...guidance].join("\n");
|
|
353
354
|
}
|
|
@@ -431,13 +432,15 @@ function waitForRunnerStartup(startupPath: string, expectedState: RunnerStartupS
|
|
|
431
432
|
return { ok: false, error: `Timed out after ${timeoutMs}ms waiting for the async runner startup state '${expectedState}'.`, startupDidNotProceed: true };
|
|
432
433
|
}
|
|
433
434
|
|
|
435
|
+
const writePrivateStartupControlJson = createAtomicJsonWriter({ mode: 0o600, ignoreCleanupErrorAfterSuccess: true });
|
|
436
|
+
|
|
434
437
|
function writeRunnerStartupControl(filePath: string, payload: { action: "ack" | "proceed"; token: string }): void {
|
|
435
438
|
// Delegate to the shared atomic JSON writer (temp file + rename, retrying
|
|
436
439
|
// transient Windows EPERM/EBUSY/EACCES locks and cleaning up the temp file
|
|
437
440
|
// on failure), so the startup handshake gets the same locking resilience as
|
|
438
441
|
// every other async control/result file. This is exercised by
|
|
439
442
|
// test/unit/atomic-json.test.ts.
|
|
440
|
-
|
|
443
|
+
writePrivateStartupControlJson(filePath, payload);
|
|
441
444
|
}
|
|
442
445
|
|
|
443
446
|
function runnerIsAlive(pid: number): boolean {
|
|
@@ -519,7 +522,7 @@ export function emitProcessTerminalEvent(ctx: AsyncExecutionContext, proof: unkn
|
|
|
519
522
|
}
|
|
520
523
|
}
|
|
521
524
|
|
|
522
|
-
function spawnRunner(cfg: object, suffix: string, cwd: string, initialStatus: Omit<AsyncStatus, "pid" | "processTerminal">, initialStatusPath: string, onProcessTerminal?: (proof: unknown) => void, requestedCwd = cwd): SpawnRunnerResult {
|
|
525
|
+
function spawnRunner(cfg: object, suffix: string, cwd: string, initialStatus: Omit<AsyncStatus, "pid" | "processTerminal">, initialStatusPath: string, onProcessTerminal?: (proof: unknown) => void, onBeforeProceed?: (runnerProcessInstanceId: string) => void, requestedCwd = cwd): SpawnRunnerResult {
|
|
523
526
|
const cwdError = preflightLaunchCwd(requestedCwd, cwd);
|
|
524
527
|
if (cwdError) return { error: cwdError };
|
|
525
528
|
|
|
@@ -636,6 +639,24 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, initialStatus: Om
|
|
|
636
639
|
});
|
|
637
640
|
} catch (error) {
|
|
638
641
|
const message = `Failed to persist initial async status: ${error instanceof Error ? error.message : String(error)}`;
|
|
642
|
+
if (launchAsyncDir) persistPreProceedStartupFailure(launchAsyncDir, launchRunId, runnerProcessInstanceId, launchSessionId, launchCompletionOwnerId, message);
|
|
643
|
+
const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
|
|
644
|
+
return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
|
|
645
|
+
}
|
|
646
|
+
try {
|
|
647
|
+
if (!launchAsyncDir) throw new Error("Async runner is missing its lifecycle directory.");
|
|
648
|
+
initializeProcessTerminal(launchAsyncDir, launchRunId, runnerProcessInstanceId);
|
|
649
|
+
} catch (error) {
|
|
650
|
+
const message = `Failed to establish async runner lifecycle sidecar: ${error instanceof Error ? error.message : String(error)}`;
|
|
651
|
+
if (launchAsyncDir) persistPreProceedStartupFailure(launchAsyncDir, launchRunId, runnerProcessInstanceId, launchSessionId, launchCompletionOwnerId, message);
|
|
652
|
+
const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
|
|
653
|
+
return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
|
|
654
|
+
}
|
|
655
|
+
try {
|
|
656
|
+
onBeforeProceed?.(runnerProcessInstanceId);
|
|
657
|
+
} catch (error) {
|
|
658
|
+
const message = `Failed to establish async runner capacity ownership: ${error instanceof Error ? error.message : String(error)}`;
|
|
659
|
+
if (launchAsyncDir) persistPreProceedStartupFailure(launchAsyncDir, launchRunId, runnerProcessInstanceId, launchSessionId, launchCompletionOwnerId, message);
|
|
639
660
|
const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
|
|
640
661
|
return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
|
|
641
662
|
}
|
|
@@ -857,14 +878,15 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
857
878
|
const task = namespaceOutputPath ? taskText : injectSingleOutputInstruction(taskText, outputPath, a);
|
|
858
879
|
|
|
859
880
|
const modelScopes = resolveModelScopesForAgent(ctx.modelScope, a.name, ctx.currentModel);
|
|
860
|
-
const
|
|
881
|
+
const modelOrigin = resolveModelOrigin({ explicitModel: s.model, agentModel: a.model, parentModel: ctx.currentModel });
|
|
882
|
+
const primaryModelFromParent = modelOrigin === "inherited";
|
|
861
883
|
const primaryModel = externalRunner ? undefined : resolveEffectiveSubagentModel(
|
|
862
884
|
s.model,
|
|
863
885
|
a.model,
|
|
864
886
|
ctx.currentModel,
|
|
865
887
|
availableModels,
|
|
866
888
|
a.modelProvider ?? ctx.currentModelProvider,
|
|
867
|
-
{ scope: modelScopes },
|
|
889
|
+
{ scope: modelScopes, source: modelOrigin === "explicit" ? "explicit" : "inherited" },
|
|
868
890
|
);
|
|
869
891
|
const thinkingOverride = flatIndex === undefined ? undefined : thinkingOverridesByFlatIndex?.[flatIndex];
|
|
870
892
|
const effectiveThinking = externalRunner ? undefined : thinkingOverride ?? a.thinking;
|
|
@@ -884,15 +906,17 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
884
906
|
}
|
|
885
907
|
const agentContract = s.agentContract ?? params.agentContract;
|
|
886
908
|
const permissionRules = resolvePermissionRules(ctx.permissions, a.permissions);
|
|
887
|
-
|
|
888
|
-
scope: modelScopes,
|
|
889
|
-
primaryModelFromParent,
|
|
890
|
-
}).flatMap((candidate) => {
|
|
891
|
-
const resolved = applyThinkingSuffix(candidate, effectiveThinking, thinkingOverride !== undefined);
|
|
892
|
-
return resolved ? [resolved] : [];
|
|
893
|
-
});
|
|
909
|
+
let modelCandidates: string[] = [];
|
|
894
910
|
if (!externalRunner) {
|
|
895
911
|
try {
|
|
912
|
+
modelCandidates = buildModelCandidates(primaryModel, a.fallbackModels, availableModels, a.modelProvider ?? ctx.currentModelProvider, {
|
|
913
|
+
scope: modelScopes,
|
|
914
|
+
primaryModelFromParent,
|
|
915
|
+
origin: modelOrigin,
|
|
916
|
+
}).flatMap((candidate) => {
|
|
917
|
+
const resolved = applyThinkingSuffix(candidate, effectiveThinking, thinkingOverride !== undefined);
|
|
918
|
+
return resolved ? [resolved] : [];
|
|
919
|
+
});
|
|
896
920
|
for (const candidate of modelCandidates) assertThinkingWithinCeiling({ model: candidate, configThinking: effectiveThinking, ceiling: thinkingCeiling, agent: a.name, runId: id });
|
|
897
921
|
} catch (error) {
|
|
898
922
|
throw new AsyncStartValidationError(error instanceof Error ? error.message : String(error));
|
|
@@ -901,6 +925,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
901
925
|
const fast = s.fast ?? params.fast ?? a.fast;
|
|
902
926
|
const toolPlan = resolvePiLaunchToolPlan({
|
|
903
927
|
tools: a.tools,
|
|
928
|
+
excludeTools: a.excludeTools,
|
|
904
929
|
allowNestedSubagents: a.allowNestedSubagents,
|
|
905
930
|
extensions: a.extensions,
|
|
906
931
|
subagentOnlyExtensions: a.subagentOnlyExtensions,
|
|
@@ -963,6 +988,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
963
988
|
...(primaryModelFromParent ? { skipPrimaryModelVerification: true } : {}),
|
|
964
989
|
...(availableModels && availableModels.length > 0 ? { modelVerificationRegistry: availableModels } : {}),
|
|
965
990
|
tools: a.tools,
|
|
991
|
+
excludeTools: a.excludeTools,
|
|
966
992
|
allowNestedSubagents: a.allowNestedSubagents,
|
|
967
993
|
extensions: a.extensions,
|
|
968
994
|
subagentOnlyExtensions: a.subagentOnlyExtensions,
|
|
@@ -998,7 +1024,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
998
1024
|
acceptanceRole: a.acceptanceRole,
|
|
999
1025
|
...(s.gateOn ? { gateOn: s.gateOn } : {}),
|
|
1000
1026
|
...(s.outputSchema ? { structuredOutputSchema: s.outputSchema } : {}),
|
|
1001
|
-
...(s.outputSchema ? { structuredOutput: createStructuredOutputRuntime(s.outputSchema, path.join(asyncDir, "structured-output"), {
|
|
1027
|
+
...(s.outputSchema ? { structuredOutput: createStructuredOutputRuntime(s.outputSchema, path.join(asyncDir, "structured-output"), { acceptanceReport: resolveAcceptanceReportMode(s.acceptance) }) } : {}),
|
|
1002
1028
|
...(resolvedToolBudget.budget ? { toolBudget: resolvedToolBudget.budget } : {}),
|
|
1003
1029
|
...(s.worktree ? { worktree: true } : {}),
|
|
1004
1030
|
};
|
|
@@ -1167,7 +1193,7 @@ export function executeAsyncChain(
|
|
|
1167
1193
|
chain: chain.map((step) => {
|
|
1168
1194
|
if (isParallelStep(step)) return { parallel: step.parallel };
|
|
1169
1195
|
if (isDynamicParallelStep(step)) return { acceptance: step.acceptance, parallel: step.parallel };
|
|
1170
|
-
return { acceptance: step.acceptance };
|
|
1196
|
+
return { acceptance: step.acceptance, outputSchema: step.outputSchema };
|
|
1171
1197
|
}),
|
|
1172
1198
|
});
|
|
1173
1199
|
if (acceptanceErrors.length > 0) return formatAsyncStartError(resultMode, acceptanceErrors.join(" "));
|
|
@@ -1328,6 +1354,7 @@ export function executeAsyncChain(
|
|
|
1328
1354
|
},
|
|
1329
1355
|
path.join(asyncDir, "status.json"),
|
|
1330
1356
|
(proof) => emitProcessTerminalEvent(ctx, proof),
|
|
1357
|
+
(runnerProcessInstanceId) => params.activeAsyncCapacity?.markStarted(runnerProcessInstanceId),
|
|
1331
1358
|
);
|
|
1332
1359
|
} catch (error) {
|
|
1333
1360
|
params.activeAsyncCapacity?.rollback();
|
|
@@ -1336,7 +1363,10 @@ export function executeAsyncChain(
|
|
|
1336
1363
|
}
|
|
1337
1364
|
|
|
1338
1365
|
if (spawnResult.error) {
|
|
1339
|
-
if (
|
|
1366
|
+
if (spawnResult.startupDidNotProceed) {
|
|
1367
|
+
if (!spawnResult.runnerProcessInstanceId || params.activeAsyncCapacity?.rollbackBeforeRunnerProceed(spawnResult.runnerProcessInstanceId) !== true) params.activeAsyncCapacity?.rollback();
|
|
1368
|
+
}
|
|
1369
|
+
else if (!spawnResult.pid || !spawnResult.runnerProcessInstanceId) params.activeAsyncCapacity?.rollback();
|
|
1340
1370
|
else params.activeAsyncCapacity?.markStarted(spawnResult.runnerProcessInstanceId);
|
|
1341
1371
|
return formatAsyncStartError(resultMode, `Failed to start async ${resultMode} '${id}': ${spawnResult.error}`);
|
|
1342
1372
|
}
|
|
@@ -1344,8 +1374,6 @@ export function executeAsyncChain(
|
|
|
1344
1374
|
params.activeAsyncCapacity?.rollback();
|
|
1345
1375
|
return formatAsyncStartError(resultMode, `Failed to start async ${resultMode} '${id}': runner identity unavailable`);
|
|
1346
1376
|
}
|
|
1347
|
-
params.activeAsyncCapacity?.markStarted(spawnResult.runnerProcessInstanceId);
|
|
1348
|
-
|
|
1349
1377
|
if (spawnResult.pid) {
|
|
1350
1378
|
const eventFirstStep = eventChain[0];
|
|
1351
1379
|
if (!eventFirstStep) {
|
|
@@ -1588,15 +1616,27 @@ export function executeAsyncSingle(
|
|
|
1588
1616
|
: "";
|
|
1589
1617
|
const taskText = readsInstruction + taskWithOutputInstruction;
|
|
1590
1618
|
const modelScopes = resolveModelScopesForAgent(ctx.modelScope, agentConfig.name, ctx.currentModel);
|
|
1591
|
-
const
|
|
1592
|
-
|
|
1593
|
-
:
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1619
|
+
const modelOrigin = resolveModelOrigin({
|
|
1620
|
+
fromParent: params.modelOverrideFromParent,
|
|
1621
|
+
storedOrigin: params.modelOrigin,
|
|
1622
|
+
explicitModel: params.modelOverrideFromParent ? undefined : params.modelOverride,
|
|
1623
|
+
agentModel: agentConfig.model,
|
|
1624
|
+
parentModel: ctx.currentModel,
|
|
1625
|
+
});
|
|
1626
|
+
let primaryModel: string | undefined;
|
|
1627
|
+
try {
|
|
1628
|
+
primaryModel = externalRunner ? undefined : modelOrigin === "inherited"
|
|
1629
|
+
? params.modelOverride ?? (ctx.currentModel ? `${ctx.currentModel.provider}/${ctx.currentModel.id}` : undefined)
|
|
1630
|
+
: resolveSubagentModelOverride(
|
|
1631
|
+
params.modelOverride ?? agentConfig.model,
|
|
1632
|
+
ctx.currentModel,
|
|
1633
|
+
availableModels,
|
|
1634
|
+
ctx.currentModelProvider,
|
|
1635
|
+
{ scope: modelScopes, source: modelOrigin === "explicit" ? "explicit" : "inherited" },
|
|
1636
|
+
);
|
|
1637
|
+
} catch (error) {
|
|
1638
|
+
return formatAsyncStartError("single", error instanceof Error ? error.message : String(error));
|
|
1639
|
+
}
|
|
1600
1640
|
const effectiveThinking = externalRunner ? undefined : params.thinkingOverride ?? agentConfig.thinking;
|
|
1601
1641
|
const model = externalRunner ? undefined : applyThinkingSuffix(primaryModel, effectiveThinking, params.thinkingOverride !== undefined);
|
|
1602
1642
|
const contextLimit = model ? findModelInfo(model, availableModels, agentConfig.modelProvider ?? ctx.currentModelProvider)?.contextWindow : undefined;
|
|
@@ -1631,20 +1671,19 @@ export function executeAsyncSingle(
|
|
|
1631
1671
|
const initialUsageBudget = usageBudgetState(params.usageBudget, undefined);
|
|
1632
1672
|
const resolvedSessionDir = params.sessionDir ?? (sessionRoot ? path.join(sessionRoot, `async-${id}`) : undefined);
|
|
1633
1673
|
const structuredOutput = params.structuredOutputSchema
|
|
1634
|
-
? createStructuredOutputRuntime(params.structuredOutputSchema, path.join(asyncDir, "structured-output"), {
|
|
1674
|
+
? createStructuredOutputRuntime(params.structuredOutputSchema, path.join(asyncDir, "structured-output"), { acceptanceReport: resolveAcceptanceReportMode(params.acceptance) })
|
|
1635
1675
|
: undefined;
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1676
|
+
let modelCandidates: string[] = [];
|
|
1677
|
+
if (!externalRunner) {
|
|
1678
|
+
try {
|
|
1679
|
+
modelCandidates = buildModelCandidates(primaryModel, agentConfig.fallbackModels, availableModels, agentConfig.modelProvider ?? ctx.currentModelProvider, {
|
|
1680
|
+
scope: modelScopes,
|
|
1681
|
+
primaryModelFromParent: modelOrigin === "inherited",
|
|
1682
|
+
origin: modelOrigin,
|
|
1683
|
+
}).flatMap((candidate) => {
|
|
1643
1684
|
const resolved = applyThinkingSuffix(candidate, effectiveThinking, params.thinkingOverride !== undefined);
|
|
1644
1685
|
return resolved ? [resolved] : [];
|
|
1645
1686
|
});
|
|
1646
|
-
if (!externalRunner) {
|
|
1647
|
-
try {
|
|
1648
1687
|
for (const candidate of modelCandidates) assertThinkingWithinCeiling({ model: candidate, configThinking: effectiveThinking, ceiling: thinkingCeiling, agent: agentConfig.name, runId: id });
|
|
1649
1688
|
} catch (error) {
|
|
1650
1689
|
return formatAsyncStartError("single", error instanceof Error ? error.message : String(error));
|
|
@@ -1652,6 +1691,7 @@ export function executeAsyncSingle(
|
|
|
1652
1691
|
}
|
|
1653
1692
|
const toolPlan = resolvePiLaunchToolPlan({
|
|
1654
1693
|
tools: agentConfig.tools,
|
|
1694
|
+
excludeTools: agentConfig.excludeTools,
|
|
1655
1695
|
allowNestedSubagents: agentConfig.allowNestedSubagents,
|
|
1656
1696
|
extensions: agentConfig.extensions,
|
|
1657
1697
|
subagentOnlyExtensions: agentConfig.subagentOnlyExtensions,
|
|
@@ -1698,6 +1738,7 @@ export function executeAsyncSingle(
|
|
|
1698
1738
|
inheritSkills: agentConfig.inheritSkills,
|
|
1699
1739
|
skills: resolvedSkills.map((skill) => skill.name),
|
|
1700
1740
|
tools: toolPlan.effectiveToolAllowlist,
|
|
1741
|
+
...(toolPlan.excludeTools.length > 0 ? { excludeTools: toolPlan.excludeTools } : {}),
|
|
1701
1742
|
extensions: toolPlan.extensionArgs,
|
|
1702
1743
|
mcpDirectTools: toolPlan.effectiveMcpTools,
|
|
1703
1744
|
...(outputPath ? { outputPath } : {}),
|
|
@@ -1730,11 +1771,13 @@ export function executeAsyncSingle(
|
|
|
1730
1771
|
...(model ? { model } : {}),
|
|
1731
1772
|
...(params.fast ?? recoveryAgentConfig.fast ? { fast: params.fast ?? recoveryAgentConfig.fast } : {}),
|
|
1732
1773
|
...(recoveryAgentConfig.modelProvider ? { modelProvider: recoveryAgentConfig.modelProvider } : {}),
|
|
1733
|
-
...(
|
|
1774
|
+
...(modelOrigin === "inherited" ? { modelOverrideFromParent: true } : {}),
|
|
1775
|
+
modelOrigin,
|
|
1734
1776
|
...(recoveryAgentConfig.fallbackModels ? { fallbackModels: [...recoveryAgentConfig.fallbackModels] } : {}),
|
|
1735
1777
|
...(effectiveThinking ? { thinking: resolveEffectiveThinking(model, effectiveThinking) } : {}),
|
|
1736
1778
|
...(thinkingCeiling ? { thinkingCeiling } : {}),
|
|
1737
1779
|
...(recoveryAgentConfig.tools ? { tools: [...recoveryAgentConfig.tools] } : {}),
|
|
1780
|
+
...(recoveryAgentConfig.excludeTools ? { excludeTools: [...recoveryAgentConfig.excludeTools] } : {}),
|
|
1738
1781
|
...(recoveryAgentConfig.allowNestedSubagents !== undefined ? { allowNestedSubagents: recoveryAgentConfig.allowNestedSubagents } : {}),
|
|
1739
1782
|
...(recoveryAgentConfig.extensions ? { extensions: [...recoveryAgentConfig.extensions] } : {}),
|
|
1740
1783
|
...(recoveryAgentConfig.subagentOnlyExtensions ? { subagentOnlyExtensions: [...recoveryAgentConfig.subagentOnlyExtensions] } : {}),
|
|
@@ -1799,9 +1842,10 @@ export function executeAsyncSingle(
|
|
|
1799
1842
|
thinking: resolveEffectiveThinking(model, effectiveThinking),
|
|
1800
1843
|
...(thinkingCeiling ? { thinkingCeiling } : {}),
|
|
1801
1844
|
modelCandidates,
|
|
1802
|
-
...(
|
|
1845
|
+
...(modelOrigin === "inherited" ? { skipPrimaryModelVerification: true } : {}),
|
|
1803
1846
|
...(availableModels && availableModels.length > 0 ? { modelVerificationRegistry: availableModels } : {}),
|
|
1804
1847
|
tools: agentConfig.tools,
|
|
1848
|
+
excludeTools: agentConfig.excludeTools,
|
|
1805
1849
|
allowNestedSubagents: agentConfig.allowNestedSubagents,
|
|
1806
1850
|
extensions: agentConfig.extensions,
|
|
1807
1851
|
subagentOnlyExtensions: agentConfig.subagentOnlyExtensions,
|
|
@@ -1830,6 +1874,7 @@ export function executeAsyncSingle(
|
|
|
1830
1874
|
...(extensionBindings ? { extensionBindings } : {}),
|
|
1831
1875
|
launchResolvedExtensions,
|
|
1832
1876
|
effectiveAcceptance: resolvedAcceptance,
|
|
1877
|
+
acceptanceInput: params.acceptance,
|
|
1833
1878
|
...(structuredOutput ? { structuredOutput } : {}),
|
|
1834
1879
|
...(params.structuredOutputSchema ? { structuredOutputSchema: params.structuredOutputSchema } : {}),
|
|
1835
1880
|
...(resolvedToolBudget.budget ? { toolBudget: resolvedToolBudget.budget } : {}),
|
|
@@ -1898,6 +1943,7 @@ export function executeAsyncSingle(
|
|
|
1898
1943
|
},
|
|
1899
1944
|
path.join(asyncDir, "status.json"),
|
|
1900
1945
|
(proof) => emitProcessTerminalEvent(ctx, proof),
|
|
1946
|
+
(runnerProcessInstanceId) => params.activeAsyncCapacity?.markStarted(runnerProcessInstanceId),
|
|
1901
1947
|
params.requestedCwd ?? runnerCwd,
|
|
1902
1948
|
);
|
|
1903
1949
|
} catch (error) {
|
|
@@ -1907,7 +1953,10 @@ export function executeAsyncSingle(
|
|
|
1907
1953
|
}
|
|
1908
1954
|
|
|
1909
1955
|
if (spawnResult.error) {
|
|
1910
|
-
if (
|
|
1956
|
+
if (spawnResult.startupDidNotProceed) {
|
|
1957
|
+
if (!spawnResult.runnerProcessInstanceId || params.activeAsyncCapacity?.rollbackBeforeRunnerProceed(spawnResult.runnerProcessInstanceId) !== true) params.activeAsyncCapacity?.rollback();
|
|
1958
|
+
}
|
|
1959
|
+
else if (!spawnResult.pid || !spawnResult.runnerProcessInstanceId) params.activeAsyncCapacity?.rollback();
|
|
1911
1960
|
else params.activeAsyncCapacity?.markStarted(spawnResult.runnerProcessInstanceId);
|
|
1912
1961
|
return formatAsyncStartError("single", `Failed to start async run '${id}': ${spawnResult.error}`);
|
|
1913
1962
|
}
|
|
@@ -1915,8 +1964,6 @@ export function executeAsyncSingle(
|
|
|
1915
1964
|
params.activeAsyncCapacity?.rollback();
|
|
1916
1965
|
return formatAsyncStartError("single", `Failed to start async run '${id}': runner identity unavailable`);
|
|
1917
1966
|
}
|
|
1918
|
-
params.activeAsyncCapacity?.markStarted(spawnResult.runnerProcessInstanceId);
|
|
1919
|
-
|
|
1920
1967
|
if (spawnResult.pid) {
|
|
1921
1968
|
if (inheritedNestedRoute && nestedAddress) {
|
|
1922
1969
|
const now = Date.now();
|