pi-subagents 0.58.0 → 0.60.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 +102 -0
- package/docs/agents.md +5 -3
- package/docs/configuration.md +4 -4
- package/docs/extension-api.md +1 -1
- package/docs/models.md +24 -1
- package/docs/observability.md +1 -1
- package/docs/tool-reference.md +93 -5
- package/docs/workflows.md +94 -3
- package/package.json +3 -1
- package/prompts/review-loop.md +2 -2
- package/skills/council-mode/SKILL.md +48 -243
- package/skills/council-mode/references/pass-contracts.md +150 -0
- package/skills/pi-subagents/SKILL.md +87 -37
- package/skills/pi-subagents/references/constraints-and-recipes.md +29 -233
- package/skills/pi-subagents/references/execution-controls.md +52 -7
- package/skills/pi-subagents/references/management-authoring-rpc.md +3 -4
- package/skills/pi-subagents/references/multi-lane-orchestration.md +13 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +54 -26
- package/skills/pi-subagents/references/review-and-validation.md +73 -0
- package/src/agents/agent-management.ts +159 -42
- package/src/agents/agent-serializer.ts +4 -2
- package/src/agents/agents.ts +67 -26
- package/src/agents/runtime-agent-registry.ts +9 -16
- package/src/api/background-work.ts +5 -1
- package/src/api/delegation.ts +0 -7
- package/src/api/preflight.ts +6 -9
- package/src/api/shared-types.ts +2 -0
- package/src/extension/fanout-child.ts +5 -3
- package/src/extension/index.ts +51 -23
- package/src/extension/public-execution.ts +16 -2
- package/src/extension/schemas.ts +36 -10
- package/src/extension/tool-description.ts +24 -5
- package/src/intercom/result-intercom.ts +2 -0
- package/src/profiles/profiles.ts +5 -6
- package/src/runs/background/active-async-capacity.ts +2 -2
- package/src/runs/background/async-execution.ts +52 -37
- package/src/runs/background/async-job-tracker.ts +17 -12
- package/src/runs/background/async-resume.ts +23 -25
- package/src/runs/background/async-status-snapshot.ts +23 -261
- package/src/runs/background/async-status.ts +110 -8
- package/src/runs/background/chain-append.ts +6 -3
- package/src/runs/background/chain-root-attachment.ts +60 -8
- package/src/runs/background/control-channel.ts +3 -2
- package/src/runs/background/fleet-view.ts +21 -11
- package/src/runs/background/notify.ts +158 -6
- package/src/runs/background/result-files.ts +2 -1
- package/src/runs/background/result-watcher.ts +2 -0
- package/src/runs/background/resume-guidance.ts +1 -1
- package/src/runs/background/retained-children.ts +1 -1
- package/src/runs/background/run-status.ts +30 -9
- package/src/runs/background/scheduled-runs.ts +86 -7
- package/src/runs/background/stale-run-reconciler.ts +10 -4
- package/src/runs/background/steering.ts +4 -14
- package/src/runs/background/subagent-runner.ts +351 -358
- package/src/runs/background/subagent-wait.ts +68 -12
- package/src/runs/background/terminal-run-index.ts +1 -1
- package/src/runs/background/wait-completions.ts +25 -1
- package/src/runs/background/wait-config.ts +23 -9
- package/src/runs/background/wait-tool.ts +9 -2
- package/src/runs/foreground/async-steering-action.ts +2 -2
- package/src/runs/foreground/execution.ts +125 -122
- package/src/runs/foreground/foreground-control.ts +3 -0
- package/src/runs/foreground/foreground-history.ts +1 -0
- package/src/runs/foreground/subagent-executor.ts +576 -240
- package/src/runs/foreground/workflow-detach-reconcile.ts +99 -200
- package/src/runs/shared/abort-recovery.ts +119 -0
- package/src/runs/shared/async-status-projection.ts +597 -0
- package/src/runs/shared/background-process-options.ts +9 -0
- package/src/runs/shared/child-identity.ts +19 -4
- package/src/runs/shared/child-launch-plan.ts +151 -0
- package/src/runs/shared/completion-evidence.ts +89 -0
- package/src/runs/shared/completion-guard.ts +1 -1
- package/src/runs/shared/dynamic-fanout.ts +2 -2
- package/src/runs/shared/host-step-status.ts +230 -0
- package/src/runs/shared/lane-metadata.ts +105 -0
- package/src/runs/shared/mcp-config-sources.ts +42 -6
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +93 -143
- package/src/runs/shared/mcp-direct-tool-grant.ts +194 -0
- package/src/runs/shared/model-fallback.ts +9 -2
- package/src/runs/shared/mutation-evidence.ts +52 -3
- package/src/runs/shared/nested-events.ts +6 -2
- package/src/runs/shared/nested-render.ts +7 -3
- package/src/runs/shared/parallel-handoff.ts +419 -7
- package/src/runs/shared/parallel-utils.ts +7 -0
- package/src/runs/shared/pi-args.ts +67 -3
- package/src/runs/shared/single-output.ts +72 -22
- package/src/runs/shared/subagent-prompt-runtime.ts +33 -6
- package/src/runs/shared/workflow-graph.ts +15 -0
- package/src/runs/shared/worktree-cleanup-plan.ts +847 -0
- package/src/runs/shared/worktree.ts +18 -0
- package/src/shared/child-session-name.ts +46 -0
- package/src/shared/extension-context.ts +24 -0
- package/src/shared/formatters.ts +5 -2
- package/src/shared/launch-contract.ts +1 -1
- package/src/shared/settings.ts +9 -103
- package/src/shared/types.ts +232 -30
- package/src/shared/utils.ts +35 -55
- package/src/slash/delegation-adapters.ts +1 -8
- package/src/slash/delegation-request.ts +0 -4
- package/src/slash/slash-bridge.ts +1 -2
- package/src/slash/slash-commands.ts +369 -90
- package/src/slash/slash-live-state.ts +22 -11
- package/src/tui/fleet-status.ts +134 -75
- package/src/tui/fleet.ts +11 -5
- package/src/tui/render-helpers.ts +31 -0
- package/src/tui/render.ts +895 -121
- package/src/watchdog/change-signature.ts +40 -1
- package/src/watchdog/turn-delta.ts +1 -1
- package/src/workflows/chat-progress.ts +6 -3
- package/src/workflows/host-command.ts +235 -0
- package/src/workflows/scripted-workflow.ts +503 -38
- package/src/workflows/workflow-child-summary.ts +9 -5
- package/src/workflows/workflow-preflight.ts +270 -0
- package/src/workflows/workflow-receipt.ts +43 -4
- package/src/workflows/workflow-settlement.ts +246 -0
- package/src/runs/shared/turn-budget.ts +0 -98
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { AcceptanceInput, AcceptanceRole, AgentRunnerConfig, OutputMode, ToolBudgetConfig
|
|
2
|
+
import type { AcceptanceInput, AcceptanceRole, AgentRunnerConfig, OutputMode, ToolBudgetConfig } from "../shared/types.ts";
|
|
3
3
|
import { CODE_OWNED_EXTERNAL_CLI_ADAPTER_LABEL, isCodeOwnedExternalCliAdapterId, parseExternalCliCapabilityNarrowing, validateCodeOwnedProfileRunner } from "../runs/shared/external-cli-contract.ts";
|
|
4
4
|
import { validateAcceptanceInput } from "../runs/shared/acceptance.ts";
|
|
5
5
|
import { validatePermissionRules, type PermissionRules } from "../runs/shared/permissions.ts";
|
|
6
6
|
import { validateToolBudgetConfig } from "../runs/shared/tool-budget.ts";
|
|
7
|
-
import { resolveTurnBudgetConfig } from "../runs/shared/turn-budget.ts";
|
|
8
7
|
import { BUILTIN_AGENT_NAMES } from "./builtin-names.ts";
|
|
9
8
|
import type { AgentConfig, AgentDefaultContext, AgentDiscoveryDiagnostic } from "./agents.ts";
|
|
10
9
|
|
|
@@ -21,6 +20,7 @@ export interface RuntimeAgentDefinition {
|
|
|
21
20
|
systemPrompt: string;
|
|
22
21
|
aliases?: readonly string[];
|
|
23
22
|
tools?: readonly string[];
|
|
23
|
+
allowNestedSubagents?: boolean;
|
|
24
24
|
mcpDirectTools?: readonly string[];
|
|
25
25
|
model?: string;
|
|
26
26
|
fallbackModels?: readonly string[];
|
|
@@ -33,7 +33,6 @@ export interface RuntimeAgentDefinition {
|
|
|
33
33
|
defaultAsync?: boolean;
|
|
34
34
|
defaultTimeoutMs?: number;
|
|
35
35
|
defaultToolTimeoutMs?: number;
|
|
36
|
-
defaultTurnBudget?: TurnBudgetConfig;
|
|
37
36
|
defaultAcceptance?: AcceptanceInput;
|
|
38
37
|
acceptanceRole?: AcceptanceRole;
|
|
39
38
|
runner?: AgentRunnerConfig;
|
|
@@ -135,8 +134,8 @@ function validateStringList(value: unknown, field: string): string[] | undefined
|
|
|
135
134
|
|
|
136
135
|
function validatePositiveInteger(value: unknown, field: string): number | undefined {
|
|
137
136
|
if (value === undefined) return undefined;
|
|
138
|
-
if (!Number.isInteger(value) ||
|
|
139
|
-
return value
|
|
137
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) throw new Error(`${field} must be a positive integer when provided.`);
|
|
138
|
+
return value;
|
|
140
139
|
}
|
|
141
140
|
|
|
142
141
|
function validateBoolean(value: unknown, field: string): boolean | undefined {
|
|
@@ -183,12 +182,6 @@ function validateRunner(value: unknown): AgentRunnerConfig | undefined {
|
|
|
183
182
|
return { type: "external-cli", ...(isCodeOwnedExternalCliAdapterId(runner.adapter) ? { adapter: runner.adapter } : {}), command: runner.command.trim(), ...(args?.length ? { args } : {}), ...(runner.promptDelivery ? { promptDelivery: "stdin" as const } : {}), ...(capabilities ? { capabilities } : {}) };
|
|
184
183
|
}
|
|
185
184
|
|
|
186
|
-
function validateTurnBudget(value: unknown): TurnBudgetConfig | undefined {
|
|
187
|
-
const result = resolveTurnBudgetConfig(value, "Runtime agent definition defaultTurnBudget");
|
|
188
|
-
if (result.error) throw new Error(result.error);
|
|
189
|
-
return result.turnBudget;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
185
|
function validateAcceptance(value: unknown): AcceptanceInput | undefined {
|
|
193
186
|
const errors = validateAcceptanceInput(value, "Runtime agent definition defaultAcceptance");
|
|
194
187
|
if (errors.length > 0) throw new Error(errors.join(" "));
|
|
@@ -205,9 +198,9 @@ function validateDefinition(value: unknown): RuntimeAgentDefinition {
|
|
|
205
198
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Runtime agent definition must be an object.");
|
|
206
199
|
const definition = value as Record<string, unknown>;
|
|
207
200
|
const supported = new Set([
|
|
208
|
-
"description", "systemPrompt", "aliases", "tools", "mcpDirectTools", "model", "fallbackModels", "thinking",
|
|
201
|
+
"description", "systemPrompt", "aliases", "tools", "allowNestedSubagents", "mcpDirectTools", "model", "fallbackModels", "thinking",
|
|
209
202
|
"systemPromptMode", "inheritProjectContext", "inheritGlobalContext", "inheritSkills", "defaultContext", "defaultAsync", "defaultTimeoutMs",
|
|
210
|
-
"defaultToolTimeoutMs", "
|
|
203
|
+
"defaultToolTimeoutMs", "defaultAcceptance", "acceptanceRole", "runner", "skills", "skillPath",
|
|
211
204
|
"extensions", "subagentOnlyExtensions", "mutationTools", "output", "outputMode", "defaultReads", "defaultProgress", "interactive",
|
|
212
205
|
"maxSubagentDepth", "completionGuard", "toolBudget", "permissions",
|
|
213
206
|
]);
|
|
@@ -225,6 +218,7 @@ function validateDefinition(value: unknown): RuntimeAgentDefinition {
|
|
|
225
218
|
if (outputMode !== undefined && outputMode !== "inline" && outputMode !== "file-only") throw new Error("Runtime agent definition outputMode must be 'inline' or 'file-only'.");
|
|
226
219
|
const aliases = validateStringList(definition.aliases, "Runtime agent definition aliases");
|
|
227
220
|
const tools = validateStringList(definition.tools, "Runtime agent definition tools");
|
|
221
|
+
const allowNestedSubagents = validateBoolean(definition.allowNestedSubagents, "Runtime agent definition allowNestedSubagents");
|
|
228
222
|
const mcpDirectTools = validateStringList(definition.mcpDirectTools, "Runtime agent definition mcpDirectTools");
|
|
229
223
|
const model = validateOptionalString(definition.model, "Runtime agent definition model");
|
|
230
224
|
const fallbackModels = validateStringList(definition.fallbackModels, "Runtime agent definition fallbackModels");
|
|
@@ -234,7 +228,6 @@ function validateDefinition(value: unknown): RuntimeAgentDefinition {
|
|
|
234
228
|
const defaultAsync = validateBoolean(definition.defaultAsync, "Runtime agent definition defaultAsync");
|
|
235
229
|
const defaultTimeoutMs = validatePositiveInteger(definition.defaultTimeoutMs, "Runtime agent definition defaultTimeoutMs");
|
|
236
230
|
const defaultToolTimeoutMs = validatePositiveInteger(definition.defaultToolTimeoutMs, "Runtime agent definition defaultToolTimeoutMs");
|
|
237
|
-
const defaultTurnBudget = validateTurnBudget(definition.defaultTurnBudget);
|
|
238
231
|
const defaultAcceptance = validateAcceptance(definition.defaultAcceptance);
|
|
239
232
|
const runner = validateRunner(definition.runner);
|
|
240
233
|
const skills = validateStringList(definition.skills, "Runtime agent definition skills");
|
|
@@ -255,6 +248,7 @@ function validateDefinition(value: unknown): RuntimeAgentDefinition {
|
|
|
255
248
|
systemPrompt: validateString(definition.systemPrompt, "Runtime agent definition systemPrompt", MAX_SYSTEM_PROMPT_LENGTH),
|
|
256
249
|
...(aliases ? { aliases } : {}),
|
|
257
250
|
...(tools ? { tools } : {}),
|
|
251
|
+
...(allowNestedSubagents !== undefined ? { allowNestedSubagents } : {}),
|
|
258
252
|
...(mcpDirectTools ? { mcpDirectTools } : {}),
|
|
259
253
|
...(model ? { model } : {}),
|
|
260
254
|
...(fallbackModels ? { fallbackModels } : {}),
|
|
@@ -267,7 +261,6 @@ function validateDefinition(value: unknown): RuntimeAgentDefinition {
|
|
|
267
261
|
...(defaultAsync !== undefined ? { defaultAsync } : {}),
|
|
268
262
|
...(defaultTimeoutMs !== undefined ? { defaultTimeoutMs } : {}),
|
|
269
263
|
...(defaultToolTimeoutMs !== undefined ? { defaultToolTimeoutMs } : {}),
|
|
270
|
-
...(defaultTurnBudget !== undefined ? { defaultTurnBudget } : {}),
|
|
271
264
|
...(defaultAcceptance !== undefined ? { defaultAcceptance } : {}),
|
|
272
265
|
...(acceptanceRole !== undefined ? { acceptanceRole: acceptanceRole as AcceptanceRole } : {}),
|
|
273
266
|
...(runner !== undefined ? { runner } : {}),
|
|
@@ -334,6 +327,7 @@ function toAgentConfig(name: string, definition: RuntimeAgentDefinition): AgentC
|
|
|
334
327
|
...(aliases ? { aliases } : {}),
|
|
335
328
|
...(definition.runner !== undefined ? { runner: definition.runner } : {}),
|
|
336
329
|
...(definition.tools !== undefined ? { tools: [...definition.tools] } : {}),
|
|
330
|
+
...(definition.allowNestedSubagents !== undefined ? { allowNestedSubagents: definition.allowNestedSubagents } : {}),
|
|
337
331
|
...(definition.mcpDirectTools !== undefined ? { mcpDirectTools: [...definition.mcpDirectTools] } : {}),
|
|
338
332
|
...(definition.model !== undefined ? { model: definition.model } : {}),
|
|
339
333
|
...(definition.fallbackModels !== undefined ? { fallbackModels: [...definition.fallbackModels] } : {}),
|
|
@@ -346,7 +340,6 @@ function toAgentConfig(name: string, definition: RuntimeAgentDefinition): AgentC
|
|
|
346
340
|
...(definition.defaultAsync !== undefined ? { defaultAsync: definition.defaultAsync } : {}),
|
|
347
341
|
...(definition.defaultTimeoutMs !== undefined ? { defaultTimeoutMs: definition.defaultTimeoutMs } : {}),
|
|
348
342
|
...(definition.defaultToolTimeoutMs !== undefined ? { defaultToolTimeoutMs: definition.defaultToolTimeoutMs } : {}),
|
|
349
|
-
...(definition.defaultTurnBudget !== undefined ? { defaultTurnBudget: definition.defaultTurnBudget } : {}),
|
|
350
343
|
...(definition.defaultAcceptance !== undefined ? { defaultAcceptance: definition.defaultAcceptance } : {}),
|
|
351
344
|
...(definition.acceptanceRole !== undefined ? { acceptanceRole: definition.acceptanceRole } : {}),
|
|
352
345
|
systemPrompt: definition.systemPrompt,
|
|
@@ -4,7 +4,11 @@ export const BACKGROUND_WORK_REGISTRY_KEY = "pi-subagents.background-work.v1";
|
|
|
4
4
|
const MAX_PROVIDER_NAME_LENGTH = 128;
|
|
5
5
|
const MAX_PROVIDERS = 100;
|
|
6
6
|
const MAX_ITEM_ID_LENGTH = 256;
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* A Pi session id is the session file path, which routinely exceeds a short
|
|
9
|
+
* identity budget in nested worktrees, so it is bounded like the other paths.
|
|
10
|
+
*/
|
|
11
|
+
const MAX_SESSION_ID_LENGTH = 4_096;
|
|
8
12
|
const MAX_WAKE_CHANNEL_LENGTH = 256;
|
|
9
13
|
const MAX_ITEMS_PER_PROVIDER = 10_000;
|
|
10
14
|
|
package/src/api/delegation.ts
CHANGED
|
@@ -7,11 +7,6 @@ export const SUBAGENT_DELEGATION_UPDATE_EVENT = "prompt-template:subagent:update
|
|
|
7
7
|
export const SUBAGENT_DELEGATION_RESPONSE_EVENT = "prompt-template:subagent:response";
|
|
8
8
|
export const SUBAGENT_DELEGATION_CANCEL_EVENT = "prompt-template:subagent:cancel";
|
|
9
9
|
|
|
10
|
-
export interface SubagentDelegationTurnBudget {
|
|
11
|
-
maxTurns: number;
|
|
12
|
-
graceTurns?: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
10
|
export interface SubagentDelegationToolBudget {
|
|
16
11
|
soft?: number;
|
|
17
12
|
hard: number;
|
|
@@ -37,7 +32,6 @@ export interface SubagentDelegationRequest {
|
|
|
37
32
|
model?: string;
|
|
38
33
|
thinking?: SubagentDelegationThinking;
|
|
39
34
|
timeoutMs?: number;
|
|
40
|
-
turnBudget?: SubagentDelegationTurnBudget;
|
|
41
35
|
toolBudget?: SubagentDelegationToolBudget;
|
|
42
36
|
skill?: string | string[] | boolean;
|
|
43
37
|
artifacts?: boolean;
|
|
@@ -69,7 +63,6 @@ export type SubagentDelegationStatus =
|
|
|
69
63
|
| "timed_out"
|
|
70
64
|
| "cancelled"
|
|
71
65
|
| "interrupted"
|
|
72
|
-
| "turn_budget_exhausted"
|
|
73
66
|
| "tool_budget_exhausted"
|
|
74
67
|
| "structured_output_failed"
|
|
75
68
|
| "acceptance_failed"
|
package/src/api/preflight.ts
CHANGED
|
@@ -14,9 +14,7 @@ import { resolveEffectiveThinking } from "../shared/model-info.ts";
|
|
|
14
14
|
import { assertThinkingWithinCeiling, decodeThinkingCeiling, intersectThinkingCeilings, SUBAGENT_THINKING_CEILING_ENV, type ThinkingLevel } from "../shared/thinking-ceiling.ts";
|
|
15
15
|
import { SUBAGENT_LIFECYCLE_ARTIFACT_VERSION, type ArtifactDirPreference, type ArtifactPaths, type JsonSchemaObject, type OutputMode } from "../shared/types.ts";
|
|
16
16
|
import { capabilityCeilingAgentRestrictionMessage, intersectSubagentCapabilityCeilings, type ResolvedSubagentCapabilityCeiling, type SubagentCapabilityAudit } from "../runs/shared/capability-ceiling.ts";
|
|
17
|
-
import { appendTurnBudgetSystemPrompt } from "../runs/shared/turn-budget.ts";
|
|
18
17
|
import { resolvePermissionRules } from "../runs/shared/permissions.ts";
|
|
19
|
-
import type { ResolvedTurnBudget } from "../shared/types.ts";
|
|
20
18
|
import type { ResolvedMcpDirectToolSelection } from "../runs/shared/mcp-direct-tool-allowlist.ts";
|
|
21
19
|
import { resolveStepBehavior } from "../shared/settings.ts";
|
|
22
20
|
import { canPreferForkFromSnapshot, resolveSubagentLaunchContext } from "../shared/fork-context.ts";
|
|
@@ -69,7 +67,6 @@ export interface SubagentLaunchContractInput {
|
|
|
69
67
|
outputMode?: OutputMode;
|
|
70
68
|
outputSchema?: JsonSchemaObject;
|
|
71
69
|
extensionBindings?: ExtensionBindings;
|
|
72
|
-
turnBudget?: ResolvedTurnBudget;
|
|
73
70
|
artifacts?: boolean;
|
|
74
71
|
artifactDir?: ArtifactDirPreference;
|
|
75
72
|
parentSessionFile?: string | null;
|
|
@@ -225,8 +222,8 @@ function taskWorkspaceScopeAuthorityDiagnostic(task: string | undefined): Subage
|
|
|
225
222
|
};
|
|
226
223
|
}
|
|
227
224
|
|
|
228
|
-
function candidateList(inputAgent: string, selected: AgentConfig | undefined, cwd: string): SubagentLaunchContractAgentCandidate[] {
|
|
229
|
-
const all = discoverAgentsAll(cwd);
|
|
225
|
+
function candidateList(inputAgent: string, selected: AgentConfig | undefined, cwd: string, provider?: string): SubagentLaunchContractAgentCandidate[] {
|
|
226
|
+
const all = discoverAgentsAll(cwd, provider);
|
|
230
227
|
return [...all.builtin, ...all.package, ...all.user, ...all.project]
|
|
231
228
|
.filter((agent) => Boolean(resolveAgentName(inputAgent, [agent]).agent))
|
|
232
229
|
.map((agent) => ({
|
|
@@ -260,7 +257,8 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
260
257
|
return { ok: false, code: "invalid_artifact_dir", message: `Unsupported artifactDir '${String(input.artifactDir)}'; expected 'project', 'session', or 'temp'.`, diagnostics };
|
|
261
258
|
}
|
|
262
259
|
const scope = resolveExecutionAgentScope(input.agentScope);
|
|
263
|
-
const
|
|
260
|
+
const parentProvider = input.preferredProvider ?? input.parentModel?.provider;
|
|
261
|
+
const discovered = discoverAgents(effectiveCwd, scope, parentProvider);
|
|
264
262
|
const resolvedAgent = resolveAgentName(input.agent, discovered.agents);
|
|
265
263
|
const ambiguousCandidates = resolvedAgent.error
|
|
266
264
|
? discovered.agents.filter((agent) => resolveAgentName(input.agent, [agent]).agent)
|
|
@@ -353,6 +351,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
353
351
|
try {
|
|
354
352
|
toolPlan = resolvePiLaunchToolPlan({
|
|
355
353
|
tools: agent.tools,
|
|
354
|
+
allowNestedSubagents: agent.allowNestedSubagents,
|
|
356
355
|
extensions: agent.extensions,
|
|
357
356
|
subagentOnlyExtensions: agent.subagentOnlyExtensions,
|
|
358
357
|
mcpDirectTools: agent.mcpDirectTools,
|
|
@@ -398,9 +397,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
398
397
|
const memoryInjection = buildAgentMemoryInjection(agent, effectiveCwd);
|
|
399
398
|
if (memoryInjection) effectiveSystemPrompt = effectiveSystemPrompt ? `${effectiveSystemPrompt}\n\n${memoryInjection}` : memoryInjection;
|
|
400
399
|
effectiveSystemPrompt = injectOutputPathSystemPrompt(effectiveSystemPrompt, outputPath, agent);
|
|
401
|
-
const
|
|
402
|
-
effectiveSystemPrompt = appendTurnBudgetSystemPrompt(effectiveSystemPrompt, turnBudget);
|
|
403
|
-
const candidates = candidateList(input.agent, agent, effectiveCwd);
|
|
400
|
+
const candidates = candidateList(input.agent, agent, effectiveCwd, parentProvider);
|
|
404
401
|
const shadowedCandidates = candidates.filter((candidate) => !candidate.selected);
|
|
405
402
|
const definitionDigest = agentDefinitionDigest(agent);
|
|
406
403
|
const contractBase: Omit<SubagentLaunchContract, "digest"> = {
|
package/src/api/shared-types.ts
CHANGED
|
@@ -158,13 +158,15 @@ export default function registerFanoutChildSubagentExtension(pi: ExtensionAPI):
|
|
|
158
158
|
registeredApis.add(pi);
|
|
159
159
|
|
|
160
160
|
const config = loadConfig();
|
|
161
|
+
const waitToolConfig = resolveWaitToolConfig(config.waitTool);
|
|
161
162
|
const state = createChildSafeState();
|
|
162
163
|
const executor = createSubagentExecutor({
|
|
163
164
|
pi,
|
|
164
165
|
state,
|
|
165
166
|
config,
|
|
166
167
|
asyncByDefault: resolveAsyncByDefault(config),
|
|
167
|
-
waitToolEnabled:
|
|
168
|
+
waitToolEnabled: waitToolConfig.enabled,
|
|
169
|
+
waitToolDefaultTimeoutMs: waitToolConfig.defaultTimeoutMs,
|
|
168
170
|
tempArtifactsDir: getArtifactsDir(null),
|
|
169
171
|
getSubagentSessionRoot,
|
|
170
172
|
expandTilde,
|
|
@@ -178,8 +180,8 @@ export default function registerFanoutChildSubagentExtension(pi: ExtensionAPI):
|
|
|
178
180
|
label: "Subagent",
|
|
179
181
|
description: [
|
|
180
182
|
"Delegate to subagents from child-safe fanout mode.",
|
|
181
|
-
"Allowed management/control actions: list, get, status, interrupt, resume, steer, doctor.",
|
|
182
|
-
"Mutating management actions (create, update, delete, eject, disable, enable, reset, grant-spawn-budget) are blocked in this mode.",
|
|
183
|
+
"Allowed management/control actions: list, get, status, lane.status, interrupt, resume, steer, doctor.",
|
|
184
|
+
"Mutating management actions (create, update, delete, eject, disable, enable, reset, grant-spawn-budget, lane.recordMerge, lane.recordSupersession) are blocked in this mode.",
|
|
183
185
|
].join("\n"),
|
|
184
186
|
parameters: params,
|
|
185
187
|
async execute(id, params, signal, onUpdate, ctx) {
|
package/src/extension/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ import { ensureAccessibleDir } from "../shared/accessible-dir.ts";
|
|
|
26
26
|
import { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from "../shared/artifacts.ts";
|
|
27
27
|
import { resolveCurrentSessionId } from "../shared/session-identity.ts";
|
|
28
28
|
import { getAgentDir } from "../shared/utils.ts";
|
|
29
|
+
import { isStaleExtensionContextError, withCachedUiContext } from "../shared/extension-context.ts";
|
|
29
30
|
import { currentCompletionOwnerId } from "../shared/completion-owner.ts";
|
|
30
31
|
import { cleanupOldChainDirs } from "../shared/settings.ts";
|
|
31
32
|
import { clearLegacyResultAnimationTimer, renderSubagentResult, renderSubagentSummary } from "../tui/render.ts";
|
|
@@ -49,7 +50,6 @@ import { registerHerdrStatusBridge, type HerdrStatusRun } from "../integrations/
|
|
|
49
50
|
import { listHerdrProjectPaneRoots, restoreHerdrProjectPaneSnapshots } from "../inspectors/herdr/project-panes.ts";
|
|
50
51
|
import { registerSubagentRpcBridge } from "./rpc.ts";
|
|
51
52
|
import { clearSlashSnapshots, getSlashRenderableSnapshot, resolveSlashMessageDetails, restoreSlashFinalSnapshots, type SlashMessageDetails } from "../slash/slash-live-state.ts";
|
|
52
|
-
import { inspectSubagentStatus } from "../runs/background/run-status.ts";
|
|
53
53
|
import { resolveWaitToolConfig } from "../runs/background/subagent-wait.ts";
|
|
54
54
|
import { registerWaitTool } from "../runs/background/wait-tool.ts";
|
|
55
55
|
import { createWaitSubscriptionManager } from "../runs/background/wait-subscriptions.ts";
|
|
@@ -61,6 +61,7 @@ import { resolveCurrentSubagentCapabilityCeiling } from "../runs/shared/capabili
|
|
|
61
61
|
import { formatDuration, shortenPath } from "../shared/formatters.ts";
|
|
62
62
|
import { applyModelExclusionsConfig, loadConfig, resolveAsyncByDefault, resolveScheduledStoreRoot } from "./config.ts";
|
|
63
63
|
import { buildSubagentToolDescription, buildSubagentToolPromptMetadata } from "./tool-description.ts";
|
|
64
|
+
import { formatWorkflowPreflightSummary, normalizeWorkflowPreflight } from "../workflows/workflow-preflight.ts";
|
|
64
65
|
import { finalizeToolResult } from "./tool-result.ts";
|
|
65
66
|
import { collectGoalContinuationNotices } from "../missions/goal-driver.ts";
|
|
66
67
|
import { restoreForegroundRunHistory } from "../runs/foreground/foreground-history.ts";
|
|
@@ -251,15 +252,25 @@ function workflowLaneKeys(script: string): string[] {
|
|
|
251
252
|
return keys;
|
|
252
253
|
}
|
|
253
254
|
|
|
254
|
-
function
|
|
255
|
+
function formatWorkflowPreflightCall(input: unknown): string {
|
|
256
|
+
if (input === undefined) return "";
|
|
257
|
+
try {
|
|
258
|
+
return formatWorkflowPreflightSummary(normalizeWorkflowPreflight(input));
|
|
259
|
+
} catch (error) {
|
|
260
|
+
return `preflight · rejected: ${error instanceof Error ? error.message : String(error)}`;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function formatWorkflowManifest(script: string, async: unknown, clarify: unknown, preflightInput?: unknown): string {
|
|
255
265
|
if (clarify === true) return "workflow script · rejected: clarify UI unsupported";
|
|
256
266
|
const keys = workflowLaneKeys(script);
|
|
257
267
|
// The workflow executor starts background work unless callers pass async:false.
|
|
258
268
|
const mode = async === false ? "foreground" : "background";
|
|
259
|
-
|
|
269
|
+
const preflight = formatWorkflowPreflightCall(preflightInput);
|
|
270
|
+
if (keys.length === 0) return `workflow script · ${mode}${preflight ? ` · ${preflight}` : ""}`;
|
|
260
271
|
const visibleKeys = keys.slice(0, 4).join(", ");
|
|
261
272
|
const remainder = keys.length > 4 ? `, +${keys.length - 4}` : "";
|
|
262
|
-
return `workflow · ${mode} · ${keys.length} lane${keys.length === 1 ? "" : "s"}: ${visibleKeys}${remainder}`;
|
|
273
|
+
return `workflow · ${mode} · ${keys.length} lane${keys.length === 1 ? "" : "s"}: ${visibleKeys}${remainder}${preflight ? ` · ${preflight}` : ""}`;
|
|
263
274
|
}
|
|
264
275
|
|
|
265
276
|
/**
|
|
@@ -292,10 +303,6 @@ function isSlashResultError(result: { details?: Details }): boolean {
|
|
|
292
303
|
return result.details?.results.some((entry) => entry.exitCode !== 0 && entry.progress?.status !== "running") || false;
|
|
293
304
|
}
|
|
294
305
|
|
|
295
|
-
function isStaleExtensionContextError(error: unknown): boolean {
|
|
296
|
-
return error instanceof Error && error.message.includes("Extension context no longer active");
|
|
297
|
-
}
|
|
298
|
-
|
|
299
306
|
function rebuildSlashResultContainer(
|
|
300
307
|
container: Container,
|
|
301
308
|
result: AgentToolResult<Details>,
|
|
@@ -464,6 +471,12 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
464
471
|
clear: () => {},
|
|
465
472
|
},
|
|
466
473
|
};
|
|
474
|
+
const withLastUiContext = <T>(run: (ctx: ExtensionContext) => T): T | undefined => {
|
|
475
|
+
const cached = state.lastUiContext;
|
|
476
|
+
return withCachedUiContext(cached, () => {
|
|
477
|
+
if (state.lastUiContext === cached) state.lastUiContext = null;
|
|
478
|
+
}, run);
|
|
479
|
+
};
|
|
467
480
|
|
|
468
481
|
const supervisorChannel = createNativeSupervisorChannel(pi, state);
|
|
469
482
|
const waitSubscriptionManager = createWaitSubscriptionManager(pi, state);
|
|
@@ -472,9 +485,17 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
472
485
|
const completionNotifier = registerSubagentNotify(pi, state, { batchConfig: config.completionBatch, ownership: resultDeliveryOwnership });
|
|
473
486
|
const fleetStatus = fleetViewEnabled
|
|
474
487
|
? new SubagentFleetStatus(state, async (itemKey) => {
|
|
475
|
-
const ctx =
|
|
476
|
-
if (!ctx
|
|
477
|
-
|
|
488
|
+
const ctx = withLastUiContext((current) => current);
|
|
489
|
+
if (!ctx) return;
|
|
490
|
+
try {
|
|
491
|
+
await openSubagentFleet(ctx, state, { initialKey: itemKey, asyncDirRoot: DIRS.async, resultsDir: DIRS.results, fleetKeybindings: config.fleetKeybindings });
|
|
492
|
+
} catch (error) {
|
|
493
|
+
if (isStaleExtensionContextError(error)) {
|
|
494
|
+
if (state.lastUiContext === ctx) state.lastUiContext = null;
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
throw error;
|
|
498
|
+
}
|
|
478
499
|
}, { placement: fleetViewPlacement })
|
|
479
500
|
: undefined;
|
|
480
501
|
let executorScheduled: ((id: string, params: SubagentParamsLike, signal: AbortSignal, ctx: ExtensionContext) => Promise<AgentToolResult<Details>>) | undefined;
|
|
@@ -503,10 +524,10 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
503
524
|
if (scheduledRunManager.observedCompletionRunIds().size > 0) return true;
|
|
504
525
|
return missionObserverResultCandidateFiles(DIRS.results).length > 0;
|
|
505
526
|
};
|
|
506
|
-
const discoverAgentsForRuntime = (cwd: string, scope: AgentScope) => {
|
|
507
|
-
const discovered = discoverAgents(cwd, scope);
|
|
527
|
+
const discoverAgentsForRuntime = (cwd: string, scope: AgentScope, preferredModelProvider?: string) => {
|
|
528
|
+
const discovered = discoverAgents(cwd, scope, preferredModelProvider);
|
|
508
529
|
if (listRuntimeAgentConfigs(pi).length === 0) return discovered;
|
|
509
|
-
const all = discoverAgentsAll(cwd);
|
|
530
|
+
const all = discoverAgentsAll(cwd, preferredModelProvider);
|
|
510
531
|
const configuredAgents: AgentConfig[] = [
|
|
511
532
|
...all.builtin,
|
|
512
533
|
...all.package,
|
|
@@ -566,6 +587,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
566
587
|
config,
|
|
567
588
|
asyncByDefault,
|
|
568
589
|
waitToolEnabled: waitToolConfig.enabled,
|
|
590
|
+
waitToolDefaultTimeoutMs: waitToolConfig.defaultTimeoutMs,
|
|
569
591
|
handleScheduledRunAction: (params, ctx) => scheduledRunManager.handleToolCall(params, ctx),
|
|
570
592
|
watchdog: mainWatchdog,
|
|
571
593
|
tempArtifactsDir,
|
|
@@ -653,7 +675,6 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
653
675
|
|
|
654
676
|
const slashBridge = registerSlashSubagentBridge({
|
|
655
677
|
events: pi.events,
|
|
656
|
-
asyncByDefault,
|
|
657
678
|
getContext: () => state.lastUiContext,
|
|
658
679
|
execute: (id, params, signal, onUpdate, ctx) =>
|
|
659
680
|
executeSubagentCollapsed(id, params, signal, onUpdate, ctx),
|
|
@@ -702,13 +723,13 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
702
723
|
}
|
|
703
724
|
if (args.workflowScript)
|
|
704
725
|
return new Text(
|
|
705
|
-
`${title}${gap}${formatWorkflowManifest(args.workflowScript, args.async, false)}`,
|
|
726
|
+
`${title}${gap}${formatWorkflowManifest(args.workflowScript, args.async, false, args.preflight)}`,
|
|
706
727
|
0,
|
|
707
728
|
0,
|
|
708
729
|
);
|
|
709
730
|
if (args.workflowScriptPath)
|
|
710
731
|
return new Text(
|
|
711
|
-
`${title}${gap}${theme.fg("accent", args.workflowScriptPath)}${args.async === true ? `${gap}${theme.fg("warning", "[async]")}` : ""}`,
|
|
732
|
+
`${title}${gap}${theme.fg("accent", args.workflowScriptPath)}${args.async === true ? `${gap}${theme.fg("warning", "[async]")}` : ""}${args.preflight !== undefined ? `${gap}${theme.fg("dim", formatWorkflowPreflightCall(args.preflight))}` : ""}`,
|
|
712
733
|
0,
|
|
713
734
|
0,
|
|
714
735
|
);
|
|
@@ -732,7 +753,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
732
753
|
|
|
733
754
|
pi.registerTool(tool);
|
|
734
755
|
|
|
735
|
-
registerWaitTool(pi, state, waitToolConfig.enabled, waitSubscriptionManager);
|
|
756
|
+
registerWaitTool(pi, state, waitToolConfig.enabled, waitSubscriptionManager, waitToolConfig.defaultTimeoutMs);
|
|
736
757
|
|
|
737
758
|
pi.on("agent_end", async (_event, ctx) => {
|
|
738
759
|
if (!ctx.hasUI) await drainOutstandingWork({ state, events: pi.events });
|
|
@@ -755,7 +776,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
755
776
|
}
|
|
756
777
|
});
|
|
757
778
|
|
|
758
|
-
registerSlashCommands(pi, state, {
|
|
779
|
+
const disposeSlashCommands = registerSlashCommands(pi, state, {
|
|
759
780
|
fleetKeybindings: config.fleetKeybindings,
|
|
760
781
|
foregroundDetachShortcut: config.foregroundDetachShortcut,
|
|
761
782
|
});
|
|
@@ -837,14 +858,13 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
837
858
|
const suspendWidgetsForCompaction = () => {
|
|
838
859
|
if (state.widgetsSuspended) return;
|
|
839
860
|
state.widgetsSuspended = true;
|
|
840
|
-
|
|
861
|
+
withLastUiContext((ctx) => ctx.ui.setWidget(WIDGET_KEY, undefined));
|
|
841
862
|
fleetStatus?.refresh();
|
|
842
863
|
};
|
|
843
864
|
const resumeWidgetsAfterCompaction = () => {
|
|
844
865
|
if (!state.widgetsSuspended) return;
|
|
845
866
|
state.widgetsSuspended = false;
|
|
846
|
-
|
|
847
|
-
if (ctx?.hasUI) refreshWidget(ctx);
|
|
867
|
+
withLastUiContext((ctx) => refreshWidget(ctx));
|
|
848
868
|
fleetStatus?.refresh();
|
|
849
869
|
};
|
|
850
870
|
|
|
@@ -939,6 +959,13 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
939
959
|
if (runtimeCleaned) return;
|
|
940
960
|
runtimeCleaned = true;
|
|
941
961
|
const shuttingDownParentSession = parentSessionEnvValue;
|
|
962
|
+
// Workflow continuations retain their launch context; abort them before
|
|
963
|
+
// teardown so a reload cannot launch through a stale context.
|
|
964
|
+
for (const controller of state.workflowControllers?.values() ?? []) {
|
|
965
|
+
if (!controller.signal.aborted) controller.abort(new Error("Workflow stopped because the extension session was replaced or reloaded."));
|
|
966
|
+
}
|
|
967
|
+
state.workflowControllers?.clear();
|
|
968
|
+
state.workflowChildStops?.clear();
|
|
942
969
|
clearRuntimeAgentsForPi(pi);
|
|
943
970
|
clearTimeout(resultIndexCleanupTimer);
|
|
944
971
|
clearTimeout(asyncRetentionTimer);
|
|
@@ -962,6 +989,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
962
989
|
// Best effort cleanup during shutdown or reload.
|
|
963
990
|
}
|
|
964
991
|
}
|
|
992
|
+
disposeSlashCommands.dispose();
|
|
965
993
|
slashBridge.cancelAll();
|
|
966
994
|
slashBridge.dispose();
|
|
967
995
|
promptTemplateBridge.cancelAll();
|
|
@@ -1031,7 +1059,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
1031
1059
|
|
|
1032
1060
|
pi.on("session_compact", () => {
|
|
1033
1061
|
const hasActiveAsyncWork = [...state.asyncJobs.values()].some((job) => job.status === "queued" || job.status === "running");
|
|
1034
|
-
if (!hasActiveAsyncWork ||
|
|
1062
|
+
if (!hasActiveAsyncWork || !withLastUiContext(() => true)) return;
|
|
1035
1063
|
pi.sendMessage(
|
|
1036
1064
|
{
|
|
1037
1065
|
customType: "subagent-compaction-resume",
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
export interface PublicSubagentExecutionParams {
|
|
2
2
|
action?: unknown;
|
|
3
|
+
capabilities?: unknown;
|
|
4
|
+
mode?: unknown;
|
|
5
|
+
repo?: unknown;
|
|
6
|
+
planId?: unknown;
|
|
3
7
|
agent?: unknown;
|
|
4
8
|
task?: unknown;
|
|
9
|
+
handoffPath?: unknown;
|
|
10
|
+
laneId?: unknown;
|
|
11
|
+
merge?: unknown;
|
|
12
|
+
supersession?: unknown;
|
|
5
13
|
step?: unknown;
|
|
6
14
|
tasks?: unknown;
|
|
7
15
|
chain?: unknown;
|
|
@@ -12,8 +20,10 @@ export interface PublicSubagentExecutionParams {
|
|
|
12
20
|
config?: unknown;
|
|
13
21
|
workflowScript?: unknown;
|
|
14
22
|
workflowScriptPath?: unknown;
|
|
23
|
+
preflight?: unknown;
|
|
15
24
|
isolation?: unknown;
|
|
16
25
|
worktree?: unknown;
|
|
26
|
+
lane?: unknown;
|
|
17
27
|
async?: unknown;
|
|
18
28
|
output?: unknown;
|
|
19
29
|
resume?: unknown;
|
|
@@ -22,6 +32,7 @@ export interface PublicSubagentExecutionParams {
|
|
|
22
32
|
workflowKey?: unknown;
|
|
23
33
|
workflowChildAsyncId?: unknown;
|
|
24
34
|
workflowAwaitAsync?: unknown;
|
|
35
|
+
workflowAwaitDetached?: unknown;
|
|
25
36
|
workflowParentDeadlineAt?: unknown;
|
|
26
37
|
suppressRoutineResultIntercom?: unknown;
|
|
27
38
|
runFanoutBudget?: unknown;
|
|
@@ -38,11 +49,14 @@ export type PublicSubagentExecutionNormalization<T> =
|
|
|
38
49
|
* Enforce the public execution cutover before requests reach the executor.
|
|
39
50
|
* Internal runs.run children and structured owned delegation bypass this boundary.
|
|
40
51
|
*/
|
|
41
|
-
export function normalizePublicSubagentExecution<T extends PublicSubagentExecutionParams>(params: T
|
|
52
|
+
export function normalizePublicSubagentExecution<T extends PublicSubagentExecutionParams>(params: T): PublicSubagentExecutionNormalization<T> {
|
|
42
53
|
if (params.workflowScript !== undefined && params.workflowScriptPath !== undefined) {
|
|
43
54
|
return { ok: false, error: "workflowScript and workflowScriptPath are mutually exclusive.", mode: "workflow" };
|
|
44
55
|
}
|
|
45
56
|
const hasWorkflowInput = params.workflowScript !== undefined || params.workflowScriptPath !== undefined;
|
|
57
|
+
if (params.preflight !== undefined && !hasWorkflowInput) {
|
|
58
|
+
return { ok: false, error: "preflight requires workflowScript or workflowScriptPath.", mode: params.action === undefined ? "workflow" : "management" };
|
|
59
|
+
}
|
|
46
60
|
const hasValidWorkflowInput = (typeof params.workflowScript === "string" && Boolean(params.workflowScript.trim()))
|
|
47
61
|
|| (typeof params.workflowScriptPath === "string" && Boolean(params.workflowScriptPath.trim()));
|
|
48
62
|
if (params.isolation !== undefined) {
|
|
@@ -59,7 +73,7 @@ export function normalizePublicSubagentExecution<T extends PublicSubagentExecuti
|
|
|
59
73
|
if (params.runFanoutBudget !== undefined || params.runFanoutAdmitted !== undefined) {
|
|
60
74
|
return { ok: false, error: "Public execution does not accept internal run fan-out fields.", mode: hasWorkflowInput ? "workflow" : "management" };
|
|
61
75
|
}
|
|
62
|
-
if (params.workflowParentRunId !== undefined || params.workflowKey !== undefined || params.workflowChildAsyncId !== undefined || params.workflowAwaitAsync !== undefined || params.workflowParentDeadlineAt !== undefined || params.suppressRoutineResultIntercom !== undefined) {
|
|
76
|
+
if (params.workflowParentRunId !== undefined || params.workflowKey !== undefined || params.workflowChildAsyncId !== undefined || params.workflowAwaitAsync !== undefined || params.workflowAwaitDetached !== undefined || params.workflowParentDeadlineAt !== undefined || params.suppressRoutineResultIntercom !== undefined) {
|
|
63
77
|
return { ok: false, error: "Public execution does not accept internal workflow child fields.", mode: hasWorkflowInput ? "workflow" : "management" };
|
|
64
78
|
}
|
|
65
79
|
const action = params.action;
|
package/src/extension/schemas.ts
CHANGED
|
@@ -101,10 +101,14 @@ const ChainGateOverride = Type.String({
|
|
|
101
101
|
description: "For chain steps with agentContract, choose whether the chain advances on execution success or acceptance success. Defaults to execution.",
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
const WorkflowLaneMetadata = Type.Object({
|
|
105
|
+
version: Type.Integer({ minimum: 1, maximum: 1 }),
|
|
106
|
+
key: Type.String({ minLength: 1, maxLength: 128, pattern: "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$" }),
|
|
107
|
+
mode: Type.Optional(Type.String({ enum: ["mutation", "review", "scout", "gate"] })),
|
|
108
|
+
sourceRef: Type.Optional(Type.String({ minLength: 1, maxLength: 128 })),
|
|
109
|
+
claims: Type.Optional(Type.Array(Type.String({ minLength: 1, maxLength: 160 }), { maxItems: 20 })),
|
|
110
|
+
outputPaths: Type.Optional(Type.Array(Type.String({ minLength: 1, maxLength: 256 }), { maxItems: 10 })),
|
|
111
|
+
}, { additionalProperties: false, description: "Optional bounded child lane metadata. Display/triage only; sourceRef is opaque and never resolved during status rendering." });
|
|
108
112
|
|
|
109
113
|
const ToolBudgetBlock = Type.Unsafe({
|
|
110
114
|
anyOf: [
|
|
@@ -129,6 +133,21 @@ const UsageBudgetOverride = Type.Object({
|
|
|
129
133
|
costUsd: Type.Optional(UsageBudgetLimitOverride),
|
|
130
134
|
}, { additionalProperties: false, description: "Optional root-only reported-usage budget. Hard limits prevent future child launches; running children are not stopped." });
|
|
131
135
|
|
|
136
|
+
const WorkflowPreflightLane = Type.Object({
|
|
137
|
+
key: Type.String({ minLength: 1, maxLength: 128 }),
|
|
138
|
+
mode: Type.Optional(Type.String({ enum: ["mutation", "review", "scout", "gate"] })),
|
|
139
|
+
decision: Type.Optional(Type.String({ maxLength: 256 })),
|
|
140
|
+
claims: Type.Optional(Type.Array(Type.String({ maxLength: 256 }), { maxItems: 16 })),
|
|
141
|
+
expectedOutput: Type.Optional(Type.String({ maxLength: 256 })),
|
|
142
|
+
independence: Type.Optional(Type.String({ maxLength: 256 })),
|
|
143
|
+
}, { additionalProperties: false });
|
|
144
|
+
|
|
145
|
+
const WorkflowPreflightOverride = Type.Object({
|
|
146
|
+
version: Type.Integer({ minimum: 1, maximum: 1 }),
|
|
147
|
+
coverage: Type.Optional(Type.String({ enum: ["complete", "partial"] })),
|
|
148
|
+
lanes: Type.Array(WorkflowPreflightLane, { maxItems: 64 }),
|
|
149
|
+
}, { additionalProperties: false, description: "Bounded display-only lane hints for workflow launch/status. V1 coverage mismatches warn but never change launch authority or execution." });
|
|
150
|
+
|
|
132
151
|
// Parallel task item (within a parallel step)
|
|
133
152
|
export const ParallelTaskSchema = Type.Object({
|
|
134
153
|
agent: Type.String(),
|
|
@@ -263,6 +282,7 @@ const SubagentParamProperties = {
|
|
|
263
282
|
action: Type.Optional(Type.String({ minLength: 1,
|
|
264
283
|
description: "Optional management/control action. Use action='validate' with workflowScript or workflowScriptPath for offline checks. Omit this field for structured single-child or workflow execution; otherwise, use it only for management/control actions."
|
|
265
284
|
})),
|
|
285
|
+
capabilities: Type.Optional(Type.Boolean({ description: "For action='list', return compact capability rows and structured details without system prompts." })),
|
|
266
286
|
name: Type.Optional(Type.String({ description: "Human-readable name for action='schedule.create'." })),
|
|
267
287
|
id: Type.Optional(Type.String({
|
|
268
288
|
description: "Run id/prefix for status/debug.run, interrupt, steer, or mission.attach-run."
|
|
@@ -273,7 +293,12 @@ const SubagentParamProperties = {
|
|
|
273
293
|
dir: Type.Optional(Type.String({
|
|
274
294
|
description: "Async run directory for status/debug.run, stop, resume, or steer."
|
|
275
295
|
})),
|
|
276
|
-
handoffPath: Type.Optional(Type.String({ description: "worktree.discard
|
|
296
|
+
handoffPath: Type.Optional(Type.String({ description: "Existing parallel handoff manifest for worktree.discard, worktree.cleanup metadata, or lane evidence actions." })),
|
|
297
|
+
repo: Type.Optional(Type.String({ description: "Repository path for action='worktree.cleanup'; defaults to cwd." })),
|
|
298
|
+
planId: Type.Optional(Type.String({ description: "Cleanup plan id reserved for a future worktree.cleanup apply action." })),
|
|
299
|
+
laneId: Type.Optional(Type.String({ minLength: 1, maxLength: 128, description: "Exact manifest run id for lane.status, lane.recordMerge, or lane.recordSupersession." })),
|
|
300
|
+
merge: Type.Optional(Type.Unsafe({ type: "object", additionalProperties: true, description: "Attested merge evidence for lane.recordMerge: prNumber, reviewedHead, mergeCommit, treeEquivalent, postMergeChecks, attestedBy, and attestedAt." })),
|
|
301
|
+
supersession: Type.Optional(Type.Unsafe({ type: "object", additionalProperties: true, description: "Attested replacement-lane evidence for lane.recordSupersession: supersededBy, attestedBy, and attestedAt." })),
|
|
277
302
|
index: Type.Optional(Type.Integer({ minimum: 0, description: "Zero-based child index for actions that target a specific child or transcript." })),
|
|
278
303
|
childId: Type.Optional(Type.String({ minLength: 1, maxLength: 256, description: "Stable child identity for child-scoped stop requests." })),
|
|
279
304
|
view: Type.Optional(Type.String({
|
|
@@ -283,7 +308,7 @@ const SubagentParamProperties = {
|
|
|
283
308
|
lines: Type.Optional(Type.Integer({ minimum: 1, maximum: 500, description: "Maximum transcript lines for action='status', view='transcript'. Defaults to 80." })),
|
|
284
309
|
topic: Type.Optional(Type.String()),
|
|
285
310
|
message: Type.Optional(Type.String({ description: "Follow-up message for resume, live guidance for steer, or optional startup prompt for project.open." })),
|
|
286
|
-
mode: Type.Optional(Type.String({ enum: ["steer", "follow_up", "auto"], description: "Delivery mode for action='steer'
|
|
311
|
+
mode: Type.Optional(Type.String({ enum: ["steer", "follow_up", "auto", "plan", "apply"], description: "Delivery mode for action='steer', or plan/apply mode for worktree.cleanup. worktree.cleanup currently supports plan only; apply/removal is not available yet." })),
|
|
287
312
|
steeringRecovery: Type.Optional(Type.Boolean({ description: "For action='steer', allow pause-and-revive recovery after a missed acknowledgment. Defaults true for direct tool calls in steer mode; extension RPC steering forces false so callers retain exact child ownership." })),
|
|
288
313
|
additional: Type.Optional(Type.Integer({ minimum: 1, description: "Positive launches to add with action='grant-spawn-budget'. Root interactive parent with native user confirmation only; total grants cannot exceed the original configured cap." })),
|
|
289
314
|
scope: Type.Optional(Type.String({ enum: ["session", "user", "project"], description: "Scope for action='watchdog.configure'. Defaults to session to avoid persistent settings writes unless user/project is explicit." })),
|
|
@@ -312,11 +337,13 @@ const SubagentParamProperties = {
|
|
|
312
337
|
],
|
|
313
338
|
description: "Agent config for create/update. Object or JSON string."
|
|
314
339
|
})),
|
|
315
|
-
workflowScript: Type.Optional(Type.String({ minLength: 1, description: "Trusted inline JavaScript statement body. Normally async unless asyncByDefault:false; set async:true when async matters. Use async:false only when the parent must block until completion, never for reviews or gates. Use explicit return for output. Use top-level await, plain helper functions, or explicit Promise chains; nested async function, arrow, and method helpers are rejected. Use await runs.run(key, {agent, task, worktree?, gate?}) or runs.run(key, {resume, task}), where resume is a retained run id or {workflowRunId,key,latest:true} from a durable async workflow receipt. Use runs.all([...]), await runs.steer(key, message, {mode?, index?, ackTimeoutMs?}), runs.status(id), runs.ref(s), emit(value), console, and return. For ordinary parallel fanout, 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. runs.steer targets a prior stable child key, never a raw run id, and must be awaited or returned. Mission workflows also have async state.get(key) and state.set(key, JSONValue). Compose sequential and parallel phases dynamically. Set worktree:true at workflow or child level for a separate managed worktree; child fields override workflow defaults. gate is one host-run command and cannot be combined with acceptance. runs.run accepts one child only. No filesystem, shell, Pi tools, or host globals." })),
|
|
340
|
+
workflowScript: Type.Optional(Type.String({ minLength: 1, description: "Trusted inline JavaScript statement body. Normally async unless asyncByDefault:false; set async:true when async matters. Use async:false only when the parent must block until completion, never for reviews or gates. Use explicit return for output. Use top-level await, plain helper functions, or explicit Promise chains; nested async function, arrow, and method helpers are rejected. Use await runs.run(key, {agent, task, worktree?, gate?}) or runs.run(key, {resume, task}), where resume is a retained run id or {workflowRunId,key,latest:true} from a durable async workflow receipt. Each workflow key identifies one result lane: use a new stable workflow key for every distinct retained resume pass; same-key calls are reused only when launch parameters are identical, and incompatible parameters are rejected. Use runs.all([...]), runs.host(key,{kind:'command',command,timeoutMs,output?,role?,provider?}), await runs.steer(key, message, {mode?, index?, ackTimeoutMs?}), runs.status(id), runs.ref(s), emit(value), console, and return. 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. For ordinary parallel fanout, 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. runs.steer targets a prior stable child key, never a raw run id, and must be awaited or returned. Mission workflows also have async state.get(key) and state.set(key, JSONValue). Compose sequential and parallel phases dynamically. Set worktree:true at workflow or child level for a separate managed worktree; child fields override workflow defaults. gate is one host-run command and cannot be combined with acceptance. runs.run accepts one child only. No filesystem, shell, Pi tools, or host globals except through runs.host." })),
|
|
316
341
|
workflowScriptPath: Type.Optional(Type.String({ minLength: 1, description: "Path to a trusted JavaScript workflow file. Mutually exclusive with workflowScript. Relative paths resolve against the request cwd. The host reads the file before the filesystem-free workflow sandbox starts." })),
|
|
342
|
+
preflight: Type.Optional(WorkflowPreflightOverride),
|
|
317
343
|
chatProgress: Type.Optional(Type.String({ enum: ["auto", "off", "live-card"], description: "WorkflowScript chat progress projection. auto shows a live in-chat card only for watched foreground workflows in the same Git repository; it is off otherwise. Explicit live-card requires same-repository async:false; async workflows should omit chatProgress or use auto/off." })),
|
|
318
344
|
isolation: Type.Optional(Type.String({ enum: ["none", "worktree"], description: "Workflow child isolation. none runs in the shared cwd; worktree requires managed git worktree isolation." })),
|
|
319
345
|
worktree: Type.Optional(Type.Boolean({ description: "Managed child isolation. true gives each workflow child a separate git worktree; an individual runs.run/runs.all item can override a workflow default with worktree:false." })),
|
|
346
|
+
lane: Type.Optional(WorkflowLaneMetadata),
|
|
320
347
|
context: Type.Optional(Type.String({
|
|
321
348
|
enum: ["fresh", "fork", "profile"],
|
|
322
349
|
description: "'fresh' or 'fork' to branch from parent session, or 'profile' to require the selected agent's declared defaultContext. Explicit fresh/fork overrides every child; profile ignores config defaultSubagentContext and fails when an agent has no defaultContext. If omitted, config defaultSubagentContext wins over each agent defaultContext; implicit fork needs a persisted parent session and leaf, else fresh. Config forkContext may prune resolved forks before spawn without adding another context value.",
|
|
@@ -325,7 +352,6 @@ const SubagentParamProperties = {
|
|
|
325
352
|
timeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "Timeout. Foreground and single async runs use config timeoutMs, else 30m; async composites have no default parent deadline. Alias maxRuntimeMs." })),
|
|
326
353
|
maxRuntimeMs: Type.Optional(Type.Integer({ minimum: 1, description: "Alias timeoutMs. Foreground and single async runs use config timeoutMs, else 30m; async composites have no default parent deadline." })),
|
|
327
354
|
toolTimeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "Optional hard per-tool-call timeout in milliseconds; known-fast built-in tools have a five-minute default." })),
|
|
328
|
-
turnBudget: Type.Optional(TurnBudgetOverride),
|
|
329
355
|
toolBudget: Type.Optional(ToolBudgetOverride),
|
|
330
356
|
usageBudget: Type.Optional(UsageBudgetOverride),
|
|
331
357
|
agentScope: Type.Optional(Type.String({ description: "Agent discovery scope: 'user', 'project', or 'both' (default: 'both'; project wins on name collisions)" })),
|
|
@@ -343,7 +369,7 @@ const SubagentParamProperties = {
|
|
|
343
369
|
{ type: "string" },
|
|
344
370
|
{ type: "boolean" },
|
|
345
371
|
],
|
|
346
|
-
description: "Default child output file (string), or false to disable. Relative paths
|
|
372
|
+
description: "Default child output file (string), or false to disable. Relative workflow child paths use managed artifact routing. Task filename prose is not an output declaration; for durable workflow handoff, return the child's outputReference, outputPathMapping, or artifactPaths.",
|
|
347
373
|
})),
|
|
348
374
|
outputMode: Type.Optional(OutputModeOverride),
|
|
349
375
|
skill: Type.Optional(SkillOverride),
|
|
@@ -375,7 +401,7 @@ const SubagentWaitParamsSchema = Type.Object({
|
|
|
375
401
|
})),
|
|
376
402
|
timeoutMs: Type.Optional(Type.Integer({
|
|
377
403
|
minimum: 1,
|
|
378
|
-
description: "Give up waiting after this many milliseconds (the runs keep going regardless). Defaults to 1800000 (30 minutes).",
|
|
404
|
+
description: "Give up waiting after this many milliseconds (the runs keep going regardless). Defaults to config waitTool.defaultTimeoutMs, then 1800000 (30 minutes). Window expiry is a non-error active-work result.",
|
|
379
405
|
})),
|
|
380
406
|
stopOnAttention: Type.Optional(Type.Boolean({
|
|
381
407
|
description: "Blocking waits stop when a run needs attention by default. Set false to keep waiting through idle or long-thinking attention; supervisor/contact requests still stop the wait.",
|