@narumitw/pi-subagents 1.0.2 → 2.0.1
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/README.md +198 -188
- package/package.json +2 -2
- package/src/agents/built-ins.ts +13 -66
- package/src/agents/catalog.ts +19 -2
- package/src/agents/discovery.ts +31 -15
- package/src/auto-transport.ts +7 -1
- package/src/child-peer-bridge.ts +124 -0
- package/src/child-peer-tools.ts +132 -0
- package/src/completion-delivery.ts +19 -5
- package/src/completion-render.ts +189 -0
- package/src/completion-routing.ts +24 -0
- package/src/config-ui.ts +11 -17
- package/src/consult-registration.ts +3 -2
- package/src/create-stateful-transport.ts +15 -2
- package/src/execution-ui.ts +0 -72
- package/src/in-process-transport.ts +39 -7
- package/src/inspect-tool.ts +3 -1
- package/src/peer-communication.ts +352 -0
- package/src/peer-transport.ts +49 -0
- package/src/persistence.ts +26 -1
- package/src/pi-args.ts +2 -0
- package/src/registry-types.ts +7 -0
- package/src/registry.ts +240 -41
- package/src/result-contract.ts +20 -5
- package/src/rpc-transport.ts +56 -26
- package/src/runner.ts +13 -1
- package/src/spawn-idempotency.ts +2 -0
- package/src/stateful-agent-view.ts +3 -1
- package/src/stateful-guidance.ts +11 -11
- package/src/stateful-safety.ts +0 -45
- package/src/stateful-tool-params.ts +11 -3
- package/src/stateful.ts +119 -47
- package/src/subagents.ts +6 -8
- package/src/subprocess-transport.ts +49 -28
- package/src/task-path.ts +65 -0
- package/src/transport-ui.ts +0 -6
- package/src/transport.ts +2 -1
- package/src/workflow-ui.ts +4 -4
- package/src/automation-contract.ts +0 -709
- package/src/automation-planner.ts +0 -65
- package/src/automation-registration.ts +0 -137
- package/src/automation-tool.ts +0 -40
- package/src/automation.ts +0 -435
- package/src/execution-profiles.ts +0 -95
- package/src/workflow-plan-compiler.ts +0 -618
- package/src/workflow-plan-patch.ts +0 -636
- package/src/workflow-planning-benchmark.ts +0 -95
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import type { AutomationRequest, WorkflowPlan } from "./automation-contract.js";
|
|
2
|
-
import { parseWorkflowPlan, WORKFLOW_PLAN_VERSION } from "./automation-contract.js";
|
|
3
|
-
import { resolveConsultResourceLaunchPolicy } from "./consult-resources.js";
|
|
4
|
-
import { DEFAULT_MAX_CONTEXT_BYTES, truncateUtf8 } from "./limits.js";
|
|
5
|
-
import type { ChildLaunchPolicy } from "./runner.js";
|
|
6
|
-
|
|
7
|
-
export const AUTOMATION_PLANNER_TOOLS = ["read", "grep", "find", "ls"] as const;
|
|
8
|
-
export const AUTOMATION_PLANNER_MAX_TIMEOUT_MS = 60_000;
|
|
9
|
-
export const AUTOMATION_PLANNER_MAX_TURNS = 8;
|
|
10
|
-
export const AUTOMATION_PLANNER_MAX_TOOL_CALLS = 16;
|
|
11
|
-
|
|
12
|
-
export interface AutomationPlannerPolicy {
|
|
13
|
-
tools: string[];
|
|
14
|
-
resources: "project-context" | "none";
|
|
15
|
-
launchPolicy: ChildLaunchPolicy;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function buildAutomationPlannerPrompt(request: AutomationRequest): string {
|
|
19
|
-
const prompt = [
|
|
20
|
-
"Compile the following explicit automation request into the smallest justified workflow proposal.",
|
|
21
|
-
`Return only JSON for ${WORKFLOW_PLAN_VERSION}; do not wrap it in Markdown or add prose.`,
|
|
22
|
-
"Do not provide hidden reasoning, chain-of-thought, or internal deliberation.",
|
|
23
|
-
"Use summary and risks only for concise, user-visible conclusions.",
|
|
24
|
-
"The executor, not this planning turn, owns identities, generations, agent selection, trust, tools, authority, workspace policy, and enforcement.",
|
|
25
|
-
"Your proposal cannot grant authority, tools, trust, network, secrets, descendants, or budget beyond the request.",
|
|
26
|
-
"Propose at most the request maxTasks and never propose workflow grandchildren.",
|
|
27
|
-
"Each task must include id, objective, dependsOn, inputArtifacts, producesArtifacts, sideEffectPolicy, readPaths, writePaths, ownershipKeys, requiredCapabilities, requiredTools, acceptanceCriteria, requiredEvidence, integrationOwner, and budget.",
|
|
28
|
-
"Use requiredVerificationRole and verifierFor only for a distinct direct verifier.",
|
|
29
|
-
"Declare dependencies with dependsOn, declare artifact id, kind, and version, and connect every consumed artifact through a direct dependency.",
|
|
30
|
-
"Use one authoritative integrationOwner for multi-task mutating work.",
|
|
31
|
-
"If required information is absent, list it in missingInputs instead of inventing it.",
|
|
32
|
-
"Request:",
|
|
33
|
-
JSON.stringify(request),
|
|
34
|
-
"Expected top-level fields: version, requestVersion, summary, missingInputs, risks, tasks.",
|
|
35
|
-
].join("\n");
|
|
36
|
-
const bounded = truncateUtf8(prompt, DEFAULT_MAX_CONTEXT_BYTES);
|
|
37
|
-
if (bounded.truncated)
|
|
38
|
-
throw new Error("Automation planner prompt exceeds the bounded context limit");
|
|
39
|
-
return bounded.text;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export async function resolveAutomationPlannerPolicy(
|
|
43
|
-
projectTrusted: boolean,
|
|
44
|
-
cwd: string,
|
|
45
|
-
resolver: typeof resolveConsultResourceLaunchPolicy = resolveConsultResourceLaunchPolicy,
|
|
46
|
-
): Promise<AutomationPlannerPolicy> {
|
|
47
|
-
const resources = projectTrusted ? "project-context" : "none";
|
|
48
|
-
const launchPolicy = await resolver(resources, projectTrusted, cwd);
|
|
49
|
-
return {
|
|
50
|
-
tools: [...AUTOMATION_PLANNER_TOOLS],
|
|
51
|
-
resources,
|
|
52
|
-
launchPolicy: {
|
|
53
|
-
...launchPolicy,
|
|
54
|
-
tools: [...AUTOMATION_PLANNER_TOOLS],
|
|
55
|
-
disableExtensions: true,
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function parseAutomationPlannerOutput(output: string): WorkflowPlan {
|
|
61
|
-
if (typeof output !== "string" || !output.trim()) {
|
|
62
|
-
throw new Error("Automation planner returned no workflow plan");
|
|
63
|
-
}
|
|
64
|
-
return parseWorkflowPlan(output.trim());
|
|
65
|
-
}
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { Text } from "@earendil-works/pi-tui";
|
|
3
|
-
import type { AutomationExecutionOptions } from "./automation.js";
|
|
4
|
-
import { type AutomationDetails, SubagentAutomationParams } from "./automation-tool.js";
|
|
5
|
-
import { cachedModuleLoader, throwIfAborted } from "./cached-module-loader.js";
|
|
6
|
-
import { renderFallbackResult, safeLine, toolHeader } from "./render-common.js";
|
|
7
|
-
|
|
8
|
-
interface AutomationExecutionModule {
|
|
9
|
-
executeAutomationRequest: typeof import("./automation.js").executeAutomationRequest;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface AutomationRegistrationDependencies {
|
|
13
|
-
loadExecution?: () => Promise<AutomationExecutionModule>;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function registerSubagentAutomation(
|
|
17
|
-
pi: ExtensionAPI,
|
|
18
|
-
options: AutomationExecutionOptions,
|
|
19
|
-
dependencies: AutomationRegistrationDependencies = {},
|
|
20
|
-
): void {
|
|
21
|
-
const loadExecution = cachedModuleLoader(
|
|
22
|
-
dependencies.loadExecution ?? (() => import("./automation.js")),
|
|
23
|
-
);
|
|
24
|
-
let generation = 0;
|
|
25
|
-
const activeControllers = new Set<AbortController>();
|
|
26
|
-
const activeWork = new Set<Promise<unknown>>();
|
|
27
|
-
const cancelAndWait = async (reason: string) => {
|
|
28
|
-
generation++;
|
|
29
|
-
for (const controller of activeControllers) {
|
|
30
|
-
controller.abort(new DOMException(reason, "AbortError"));
|
|
31
|
-
}
|
|
32
|
-
await Promise.allSettled([...activeWork]);
|
|
33
|
-
};
|
|
34
|
-
pi.on("session_start", () => cancelAndWait("Autonomous workflow session replaced"));
|
|
35
|
-
pi.on("session_shutdown", () => cancelAndWait("Autonomous workflow session shut down"));
|
|
36
|
-
const definition: ToolDefinition<typeof SubagentAutomationParams, AutomationDetails> = {
|
|
37
|
-
name: "subagent_auto",
|
|
38
|
-
label: "Autonomous Subagent Workflow",
|
|
39
|
-
description: [
|
|
40
|
-
"Explicitly opt in to one bounded read-only planning turn that compiles a high-level objective into the smallest justified existing workflow.",
|
|
41
|
-
"The deterministic compiler may return parent-owned work, request missing input, or reject without launching execution workers.",
|
|
42
|
-
"Mutating workflows require an authoritative integration path and an independent verifier, allow at most two concurrent mutating workers, and never allow workflow grandchildren.",
|
|
43
|
-
"The first version routes only built-in and user-scoped agents; use caller-authored workflow mode for project-local agents.",
|
|
44
|
-
].join(" "),
|
|
45
|
-
promptSnippet:
|
|
46
|
-
"Explicitly compile one high-level objective into a bounded capability-matched workflow",
|
|
47
|
-
promptGuidelines: [
|
|
48
|
-
"Use subagent_auto only when the caller explicitly opts into autonomous workflow planning.",
|
|
49
|
-
"Provide a complete authority ceiling and aggregate budget; parent-owned and insufficient-evidence results launch no execution workers.",
|
|
50
|
-
"Use caller-authored subagent workflow mode as the compatibility fallback when deterministic task control is required.",
|
|
51
|
-
],
|
|
52
|
-
parameters: SubagentAutomationParams,
|
|
53
|
-
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
54
|
-
const ownerGeneration = generation;
|
|
55
|
-
const controller = new AbortController();
|
|
56
|
-
activeControllers.add(controller);
|
|
57
|
-
const combined = combineSignals(signal, controller.signal);
|
|
58
|
-
const work = (async () => {
|
|
59
|
-
throwIfAborted(combined.signal, "Autonomous workflow loading was cancelled");
|
|
60
|
-
let executionModule: AutomationExecutionModule;
|
|
61
|
-
try {
|
|
62
|
-
executionModule = await loadExecution();
|
|
63
|
-
} catch (error) {
|
|
64
|
-
throwIfAborted(combined.signal, "Autonomous workflow loading was cancelled");
|
|
65
|
-
throw error;
|
|
66
|
-
}
|
|
67
|
-
throwIfAborted(combined.signal, "Autonomous workflow loading was cancelled");
|
|
68
|
-
if (ownerGeneration !== generation) {
|
|
69
|
-
throw new DOMException("Autonomous workflow owner was replaced", "AbortError");
|
|
70
|
-
}
|
|
71
|
-
return executionModule.executeAutomationRequest(
|
|
72
|
-
toolCallId,
|
|
73
|
-
params,
|
|
74
|
-
combined.signal,
|
|
75
|
-
onUpdate,
|
|
76
|
-
ctx,
|
|
77
|
-
options,
|
|
78
|
-
() => ownerGeneration === generation,
|
|
79
|
-
);
|
|
80
|
-
})();
|
|
81
|
-
activeWork.add(work);
|
|
82
|
-
try {
|
|
83
|
-
return await work;
|
|
84
|
-
} finally {
|
|
85
|
-
combined.dispose();
|
|
86
|
-
activeControllers.delete(controller);
|
|
87
|
-
activeWork.delete(work);
|
|
88
|
-
}
|
|
89
|
-
},
|
|
90
|
-
renderCall(args, theme) {
|
|
91
|
-
const request = (args as { request?: { objective?: string; version?: string } }).request;
|
|
92
|
-
return new Text(
|
|
93
|
-
toolHeader(theme, "subagent_auto", request?.objective, [request?.version ?? "request"]),
|
|
94
|
-
0,
|
|
95
|
-
0,
|
|
96
|
-
);
|
|
97
|
-
},
|
|
98
|
-
renderResult(result, renderOptions, theme) {
|
|
99
|
-
const status = safeLine(result.details?.status, "completed", 128);
|
|
100
|
-
return renderFallbackResult(
|
|
101
|
-
result,
|
|
102
|
-
renderOptions,
|
|
103
|
-
theme,
|
|
104
|
-
result.details?.isError === true ||
|
|
105
|
-
status.endsWith("failed") ||
|
|
106
|
-
status.endsWith("rejected"),
|
|
107
|
-
);
|
|
108
|
-
},
|
|
109
|
-
};
|
|
110
|
-
pi.registerTool<typeof SubagentAutomationParams, AutomationDetails>(definition);
|
|
111
|
-
pi.on("tool_result", (event) => {
|
|
112
|
-
if (event.toolName !== "subagent_auto") return;
|
|
113
|
-
if ((event.details as AutomationDetails | undefined)?.isError) return { isError: true };
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function combineSignals(
|
|
118
|
-
external: AbortSignal | undefined,
|
|
119
|
-
owned: AbortSignal,
|
|
120
|
-
): { signal: AbortSignal; dispose(): void } {
|
|
121
|
-
const controller = new AbortController();
|
|
122
|
-
const signals = [external, owned].filter((value): value is AbortSignal => value !== undefined);
|
|
123
|
-
const listeners = signals.map((source) => {
|
|
124
|
-
const listener = () => {
|
|
125
|
-
if (!controller.signal.aborted) controller.abort(source.reason);
|
|
126
|
-
};
|
|
127
|
-
if (source.aborted) listener();
|
|
128
|
-
else source.addEventListener("abort", listener, { once: true });
|
|
129
|
-
return { source, listener };
|
|
130
|
-
});
|
|
131
|
-
return {
|
|
132
|
-
signal: controller.signal,
|
|
133
|
-
dispose() {
|
|
134
|
-
for (const { source, listener } of listeners) source.removeEventListener("abort", listener);
|
|
135
|
-
},
|
|
136
|
-
};
|
|
137
|
-
}
|
package/src/automation-tool.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { type Static, Type } from "typebox";
|
|
2
|
-
import { AutomationRequestSchema } from "./automation-contract.js";
|
|
3
|
-
import type { SubagentDetails } from "./runner.js";
|
|
4
|
-
import type { CompiledWorkflowPlan } from "./workflow-plan-compiler.js";
|
|
5
|
-
|
|
6
|
-
export const SubagentAutomationParams = Type.Object(
|
|
7
|
-
{ request: AutomationRequestSchema },
|
|
8
|
-
{ additionalProperties: false },
|
|
9
|
-
);
|
|
10
|
-
export type SubagentAutomationParams = Static<typeof SubagentAutomationParams>;
|
|
11
|
-
|
|
12
|
-
export interface AutomationDetails {
|
|
13
|
-
status:
|
|
14
|
-
| "planning"
|
|
15
|
-
| "planner-failed"
|
|
16
|
-
| "parent-owned"
|
|
17
|
-
| "needs-input"
|
|
18
|
-
| "compiler-rejected"
|
|
19
|
-
| "executed";
|
|
20
|
-
requestVersion: string;
|
|
21
|
-
planVersion?: string;
|
|
22
|
-
planId?: string;
|
|
23
|
-
workflowGeneration?: number;
|
|
24
|
-
revision?: number;
|
|
25
|
-
childCount: number;
|
|
26
|
-
reasonCodes: string[];
|
|
27
|
-
missingInputs?: string[];
|
|
28
|
-
planner?: {
|
|
29
|
-
agent: string;
|
|
30
|
-
tools: string[];
|
|
31
|
-
resources: "project-context" | "none";
|
|
32
|
-
timeoutMs: number;
|
|
33
|
-
maxTurns: number;
|
|
34
|
-
maxToolCalls: number;
|
|
35
|
-
failed?: boolean;
|
|
36
|
-
};
|
|
37
|
-
compiled?: CompiledWorkflowPlan;
|
|
38
|
-
execution?: SubagentDetails;
|
|
39
|
-
isError?: boolean;
|
|
40
|
-
}
|
package/src/automation.ts
DELETED
|
@@ -1,435 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import type { AgentToolResult, AgentToolUpdateCallback } from "@earendil-works/pi-agent-core";
|
|
4
|
-
import { type ExtensionContext, getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
5
|
-
import { getBuiltInAgent } from "./agents/built-ins.js";
|
|
6
|
-
import { discoverAgents } from "./agents/discovery.js";
|
|
7
|
-
import type { SubagentSettings } from "./agents/types.js";
|
|
8
|
-
import { parseAutomationRequest, type WorkflowPlan } from "./automation-contract.js";
|
|
9
|
-
import {
|
|
10
|
-
AUTOMATION_PLANNER_MAX_TIMEOUT_MS,
|
|
11
|
-
AUTOMATION_PLANNER_MAX_TOOL_CALLS,
|
|
12
|
-
AUTOMATION_PLANNER_MAX_TURNS,
|
|
13
|
-
AUTOMATION_PLANNER_TOOLS,
|
|
14
|
-
buildAutomationPlannerPrompt,
|
|
15
|
-
parseAutomationPlannerOutput,
|
|
16
|
-
resolveAutomationPlannerPolicy,
|
|
17
|
-
} from "./automation-planner.js";
|
|
18
|
-
import type { AutomationDetails, SubagentAutomationParams } from "./automation-tool.js";
|
|
19
|
-
import {
|
|
20
|
-
assertDelegationTargetAllowed,
|
|
21
|
-
resolveSubagentTarget,
|
|
22
|
-
targetPolicyAudit,
|
|
23
|
-
} from "./cwd-policy.js";
|
|
24
|
-
import { executeSubagent } from "./execution.js";
|
|
25
|
-
import {
|
|
26
|
-
getResultFinalOutput,
|
|
27
|
-
isResultError,
|
|
28
|
-
runSingleAgent,
|
|
29
|
-
type SingleResult,
|
|
30
|
-
type SubagentDetails,
|
|
31
|
-
} from "./runner.js";
|
|
32
|
-
import { boundedPrivateText } from "./safe-text.js";
|
|
33
|
-
import {
|
|
34
|
-
DEFAULT_DELEGATION_CWD_POLICY,
|
|
35
|
-
resolveBlockingMaxParallelTasks,
|
|
36
|
-
} from "./settings/inspection.js";
|
|
37
|
-
import {
|
|
38
|
-
type CompiledWorkflowPlan,
|
|
39
|
-
compileWorkflowPlan,
|
|
40
|
-
type WorkflowPlanCompilerResult,
|
|
41
|
-
} from "./workflow-plan-compiler.js";
|
|
42
|
-
import { AutomationPlanPersistence, createWorkflowPlanRecord } from "./workflow-plan-patch.js";
|
|
43
|
-
import { createBlockingWorkLedger, resolveWorkflowTasks } from "./workflow-planning.js";
|
|
44
|
-
|
|
45
|
-
export { registerSubagentAutomation } from "./automation-registration.js";
|
|
46
|
-
export type { AutomationDetails } from "./automation-tool.js";
|
|
47
|
-
export { SubagentAutomationParams } from "./automation-tool.js";
|
|
48
|
-
|
|
49
|
-
export interface AutomationPlannerRequest {
|
|
50
|
-
prompt: string;
|
|
51
|
-
ctx: ExtensionContext;
|
|
52
|
-
signal: AbortSignal;
|
|
53
|
-
settings: SubagentSettings | undefined;
|
|
54
|
-
timeoutMs: number;
|
|
55
|
-
maxTurns: number;
|
|
56
|
-
maxToolCalls: number;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export interface AutomationExecutionOptions {
|
|
60
|
-
getSettings(): SubagentSettings | undefined;
|
|
61
|
-
runPlanner?: (request: AutomationPlannerRequest) => Promise<string>;
|
|
62
|
-
runWorkflow?: (
|
|
63
|
-
params: Parameters<typeof executeSubagent>[1],
|
|
64
|
-
signal: AbortSignal,
|
|
65
|
-
ctx: ExtensionContext,
|
|
66
|
-
) => ReturnType<typeof executeSubagent>;
|
|
67
|
-
persistCompiled?: (compiled: CompiledWorkflowPlan, ctx: ExtensionContext) => Promise<void>;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export async function executeAutomationRequest(
|
|
71
|
-
toolCallId: string,
|
|
72
|
-
params: SubagentAutomationParams,
|
|
73
|
-
signal: AbortSignal,
|
|
74
|
-
onUpdate: AgentToolUpdateCallback<AutomationDetails> | undefined,
|
|
75
|
-
ctx: ExtensionContext,
|
|
76
|
-
options: AutomationExecutionOptions,
|
|
77
|
-
isCurrent: () => boolean = () => true,
|
|
78
|
-
): Promise<AgentToolResult<AutomationDetails> & { isError?: boolean }> {
|
|
79
|
-
validateAutomationToolParams(params);
|
|
80
|
-
const request = parseAutomationRequest(params.request);
|
|
81
|
-
assertCurrent(signal, isCurrent);
|
|
82
|
-
const settings = options.getSettings();
|
|
83
|
-
const plannerBudget = reservePlannerBudget(request.aggregateBudget);
|
|
84
|
-
const plannerDetails: NonNullable<AutomationDetails["planner"]> = {
|
|
85
|
-
agent: "planner",
|
|
86
|
-
tools: [...AUTOMATION_PLANNER_TOOLS],
|
|
87
|
-
resources: ctx.isProjectTrusted() ? "project-context" : "none",
|
|
88
|
-
...plannerBudget,
|
|
89
|
-
};
|
|
90
|
-
const depth = Number.parseInt(process.env.PI_SUBAGENT_DEPTH ?? "0", 10) || 0;
|
|
91
|
-
if (depth > 0) {
|
|
92
|
-
return {
|
|
93
|
-
content: [
|
|
94
|
-
{
|
|
95
|
-
type: "text",
|
|
96
|
-
text: "Automation compiler rejected workflow recursion before planning or execution.",
|
|
97
|
-
},
|
|
98
|
-
],
|
|
99
|
-
details: {
|
|
100
|
-
status: "compiler-rejected",
|
|
101
|
-
requestVersion: request.version,
|
|
102
|
-
childCount: 0,
|
|
103
|
-
reasonCodes: ["workflow-recursion-disabled"],
|
|
104
|
-
planner: plannerDetails,
|
|
105
|
-
isError: true,
|
|
106
|
-
},
|
|
107
|
-
isError: true,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
const executionRequest = reserveExecutionBudget(
|
|
111
|
-
request,
|
|
112
|
-
plannerBudget,
|
|
113
|
-
resolveBlockingMaxParallelTasks(settings),
|
|
114
|
-
);
|
|
115
|
-
if (!executionRequest) {
|
|
116
|
-
return nonLaunchResult(
|
|
117
|
-
"compiler-rejected",
|
|
118
|
-
request.version,
|
|
119
|
-
["execution-budget-exhausted"],
|
|
120
|
-
plannerDetails,
|
|
121
|
-
new Error("Aggregate budget cannot fund both planning and execution"),
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
onUpdate?.({
|
|
125
|
-
content: [{ type: "text", text: "Planning a bounded autonomous workflow." }],
|
|
126
|
-
details: {
|
|
127
|
-
status: "planning",
|
|
128
|
-
requestVersion: request.version,
|
|
129
|
-
childCount: 0,
|
|
130
|
-
reasonCodes: [],
|
|
131
|
-
planner: plannerDetails,
|
|
132
|
-
},
|
|
133
|
-
});
|
|
134
|
-
let proposal: WorkflowPlan;
|
|
135
|
-
try {
|
|
136
|
-
const prompt = buildAutomationPlannerPrompt(request);
|
|
137
|
-
const runPlanner = options.runPlanner ?? runDefaultPlanner;
|
|
138
|
-
const output = await runPlanner({
|
|
139
|
-
prompt,
|
|
140
|
-
ctx,
|
|
141
|
-
signal,
|
|
142
|
-
settings,
|
|
143
|
-
...plannerBudget,
|
|
144
|
-
});
|
|
145
|
-
assertCurrent(signal, isCurrent);
|
|
146
|
-
proposal = parseAutomationPlannerOutput(output);
|
|
147
|
-
} catch (error) {
|
|
148
|
-
if (signal.aborted || !isCurrent()) throw abortError("Automation planning was cancelled");
|
|
149
|
-
return nonLaunchResult(
|
|
150
|
-
"planner-failed",
|
|
151
|
-
request.version,
|
|
152
|
-
["planner-failed"],
|
|
153
|
-
plannerDetails,
|
|
154
|
-
error,
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
const target = resolveSubagentTarget({
|
|
158
|
-
workspace: ctx.cwd,
|
|
159
|
-
requestedCwd: ctx.cwd,
|
|
160
|
-
currentProjectTrusted: ctx.isProjectTrusted(),
|
|
161
|
-
});
|
|
162
|
-
try {
|
|
163
|
-
assertDelegationTargetAllowed(
|
|
164
|
-
target,
|
|
165
|
-
settings?.cwdPolicy?.delegation ?? DEFAULT_DELEGATION_CWD_POLICY,
|
|
166
|
-
);
|
|
167
|
-
} catch (error) {
|
|
168
|
-
return nonLaunchResult(
|
|
169
|
-
"compiler-rejected",
|
|
170
|
-
request.version,
|
|
171
|
-
["target-policy-rejected"],
|
|
172
|
-
plannerDetails,
|
|
173
|
-
error,
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
const agents = discoverAgents(ctx.cwd, "user", settings).agents;
|
|
177
|
-
const compiled = compileWorkflowPlan({
|
|
178
|
-
request: executionRequest,
|
|
179
|
-
proposal,
|
|
180
|
-
agents,
|
|
181
|
-
target: targetPolicyAudit(target),
|
|
182
|
-
depth,
|
|
183
|
-
});
|
|
184
|
-
assertCurrent(signal, isCurrent);
|
|
185
|
-
if (compiled.status !== "compiled") {
|
|
186
|
-
return compilerNonLaunch(request.version, proposal.version, compiled, plannerDetails);
|
|
187
|
-
}
|
|
188
|
-
try {
|
|
189
|
-
if (options.persistCompiled) await options.persistCompiled(compiled, ctx);
|
|
190
|
-
else await persistCompiledWorkflow(compiled, ctx, settings);
|
|
191
|
-
assertCurrent(signal, isCurrent);
|
|
192
|
-
} catch (error) {
|
|
193
|
-
return nonLaunchResult(
|
|
194
|
-
"compiler-rejected",
|
|
195
|
-
request.version,
|
|
196
|
-
["plan-persistence-failed"],
|
|
197
|
-
plannerDetails,
|
|
198
|
-
error,
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
const workflowParams = {
|
|
202
|
-
workflow: compiled.workflow,
|
|
203
|
-
agentScope: "user" as const,
|
|
204
|
-
totalTimeoutMs: executionRequest.aggregateBudget.timeoutMs,
|
|
205
|
-
};
|
|
206
|
-
const execute =
|
|
207
|
-
options.runWorkflow ??
|
|
208
|
-
((workflow, workflowSignal, workflowContext) =>
|
|
209
|
-
executeSubagent(toolCallId, workflow, workflowSignal, undefined, workflowContext, settings));
|
|
210
|
-
const result = await execute(workflowParams, signal, ctx);
|
|
211
|
-
assertCurrent(signal, isCurrent);
|
|
212
|
-
const details: AutomationDetails = {
|
|
213
|
-
status: "executed",
|
|
214
|
-
requestVersion: request.version,
|
|
215
|
-
planVersion: proposal.version,
|
|
216
|
-
planId: compiled.planId,
|
|
217
|
-
workflowGeneration: compiled.workflowGeneration,
|
|
218
|
-
revision: compiled.revision,
|
|
219
|
-
childCount: compiled.childCount,
|
|
220
|
-
reasonCodes: [],
|
|
221
|
-
planner: plannerDetails,
|
|
222
|
-
compiled,
|
|
223
|
-
execution: result.details,
|
|
224
|
-
...(result.isError ? { isError: true } : {}),
|
|
225
|
-
};
|
|
226
|
-
return {
|
|
227
|
-
content: result.content,
|
|
228
|
-
details,
|
|
229
|
-
...(result.usage ? { usage: result.usage } : {}),
|
|
230
|
-
...(result.isError ? { isError: true } : {}),
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
async function runDefaultPlanner(request: AutomationPlannerRequest): Promise<string> {
|
|
235
|
-
const planner = getBuiltInAgent("planner");
|
|
236
|
-
if (!planner) throw new Error("The built-in automation planner is unavailable");
|
|
237
|
-
const policy = await resolveAutomationPlannerPolicy(
|
|
238
|
-
request.ctx.isProjectTrusted(),
|
|
239
|
-
request.ctx.cwd,
|
|
240
|
-
);
|
|
241
|
-
if (request.signal.aborted) throw abortError("Automation planner was cancelled before launch");
|
|
242
|
-
const child = {
|
|
243
|
-
...planner,
|
|
244
|
-
tools: [...AUTOMATION_PLANNER_TOOLS],
|
|
245
|
-
systemPrompt: [
|
|
246
|
-
planner.systemPrompt,
|
|
247
|
-
"This planning turn is read-only and cannot execute delegated work or create descendants.",
|
|
248
|
-
"Return one exact versioned JSON workflow proposal without Markdown fences.",
|
|
249
|
-
].join("\n\n"),
|
|
250
|
-
};
|
|
251
|
-
const result = await runSingleAgent(
|
|
252
|
-
request.ctx.cwd,
|
|
253
|
-
[child],
|
|
254
|
-
child.name,
|
|
255
|
-
request.prompt,
|
|
256
|
-
request.ctx.cwd,
|
|
257
|
-
undefined,
|
|
258
|
-
request.signal,
|
|
259
|
-
child.thinkingLevel,
|
|
260
|
-
request.timeoutMs,
|
|
261
|
-
undefined,
|
|
262
|
-
(results): SubagentDetails => ({
|
|
263
|
-
mode: "single",
|
|
264
|
-
agentScope: "user",
|
|
265
|
-
projectAgentsDir: null,
|
|
266
|
-
results,
|
|
267
|
-
}),
|
|
268
|
-
undefined,
|
|
269
|
-
{
|
|
270
|
-
...policy.launchPolicy,
|
|
271
|
-
tools: [...AUTOMATION_PLANNER_TOOLS],
|
|
272
|
-
turnLimits: {
|
|
273
|
-
maxTurns: request.maxTurns,
|
|
274
|
-
maxToolCalls: request.maxToolCalls,
|
|
275
|
-
},
|
|
276
|
-
},
|
|
277
|
-
);
|
|
278
|
-
if (isResultError(result)) throw plannerFailure(result);
|
|
279
|
-
return getResultFinalOutput(result);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
async function persistCompiledWorkflow(
|
|
283
|
-
compiled: CompiledWorkflowPlan,
|
|
284
|
-
ctx: ExtensionContext,
|
|
285
|
-
settings: SubagentSettings | undefined,
|
|
286
|
-
): Promise<void> {
|
|
287
|
-
const agents = discoverAgents(ctx.cwd, "user", settings).agents;
|
|
288
|
-
const resolved = resolveWorkflowTasks({ workflow: compiled.workflow }, agents);
|
|
289
|
-
const ledger = createBlockingWorkLedger({ workflow: compiled.workflow }, resolved, undefined);
|
|
290
|
-
if (!ledger) throw new Error("Compiled automation workflow did not create a WorkItem ledger");
|
|
291
|
-
const owner =
|
|
292
|
-
ctx.sessionManager.getSessionId?.() ??
|
|
293
|
-
ctx.sessionManager.getSessionFile?.() ??
|
|
294
|
-
`ephemeral:${ctx.cwd}`;
|
|
295
|
-
const stable = createHash("sha256").update(`session:${owner}`).digest("hex").slice(0, 24);
|
|
296
|
-
const filePath = path.join(getAgentDir(), "pi-subagents-workflows", `automation-${stable}.json`);
|
|
297
|
-
await new AutomationPlanPersistence(filePath).save({
|
|
298
|
-
record: createWorkflowPlanRecord(compiled),
|
|
299
|
-
ledger: ledger.snapshot(),
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function validateAutomationToolParams(value: unknown): asserts value is SubagentAutomationParams {
|
|
304
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
305
|
-
throw new Error("subagent_auto parameters must be an object");
|
|
306
|
-
}
|
|
307
|
-
const keys = Object.keys(value as Record<string, unknown>);
|
|
308
|
-
if (keys.length !== 1 || keys[0] !== "request") {
|
|
309
|
-
throw new Error("subagent_auto accepts exactly one request field");
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
function compilerNonLaunch(
|
|
314
|
-
requestVersion: string,
|
|
315
|
-
planVersion: string,
|
|
316
|
-
compiled: Exclude<WorkflowPlanCompilerResult, CompiledWorkflowPlan>,
|
|
317
|
-
planner: NonNullable<AutomationDetails["planner"]>,
|
|
318
|
-
): AgentToolResult<AutomationDetails> & { isError?: boolean } {
|
|
319
|
-
const status =
|
|
320
|
-
compiled.status === "parent-owned"
|
|
321
|
-
? "parent-owned"
|
|
322
|
-
: compiled.status === "needs-input"
|
|
323
|
-
? "needs-input"
|
|
324
|
-
: "compiler-rejected";
|
|
325
|
-
const isError = status === "compiler-rejected";
|
|
326
|
-
return {
|
|
327
|
-
content: [
|
|
328
|
-
{
|
|
329
|
-
type: "text",
|
|
330
|
-
text:
|
|
331
|
-
status === "parent-owned"
|
|
332
|
-
? "Automation decision: keep this objective parent-owned; no execution workers were launched."
|
|
333
|
-
: status === "needs-input"
|
|
334
|
-
? `Automation needs input: ${(compiled.missingInputs ?? []).join(", ")}`
|
|
335
|
-
: `Automation compiler rejected the proposal: ${compiled.reasonCodes.join(", ")}`,
|
|
336
|
-
},
|
|
337
|
-
],
|
|
338
|
-
details: {
|
|
339
|
-
status,
|
|
340
|
-
requestVersion,
|
|
341
|
-
planVersion,
|
|
342
|
-
childCount: 0,
|
|
343
|
-
reasonCodes: [...compiled.reasonCodes],
|
|
344
|
-
...(compiled.missingInputs ? { missingInputs: [...compiled.missingInputs] } : {}),
|
|
345
|
-
planner,
|
|
346
|
-
...(isError ? { isError: true } : {}),
|
|
347
|
-
},
|
|
348
|
-
...(isError ? { isError: true } : {}),
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
function nonLaunchResult(
|
|
353
|
-
status: "planner-failed" | "compiler-rejected",
|
|
354
|
-
requestVersion: string,
|
|
355
|
-
reasonCodes: string[],
|
|
356
|
-
planner: NonNullable<AutomationDetails["planner"]>,
|
|
357
|
-
error: unknown,
|
|
358
|
-
): AgentToolResult<AutomationDetails> & { isError: true } {
|
|
359
|
-
const message = boundedPrivateText(
|
|
360
|
-
error instanceof Error ? error.message : String(error),
|
|
361
|
-
2 * 1024,
|
|
362
|
-
);
|
|
363
|
-
return {
|
|
364
|
-
content: [{ type: "text", text: `Automation ${status}: ${message}` }],
|
|
365
|
-
details: {
|
|
366
|
-
status,
|
|
367
|
-
requestVersion,
|
|
368
|
-
childCount: 0,
|
|
369
|
-
reasonCodes,
|
|
370
|
-
planner: { ...planner, ...(status === "planner-failed" ? { failed: true } : {}) },
|
|
371
|
-
isError: true,
|
|
372
|
-
},
|
|
373
|
-
isError: true,
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
function reservePlannerBudget(budget: {
|
|
378
|
-
timeoutMs: number;
|
|
379
|
-
maxTurns: number;
|
|
380
|
-
maxToolCalls: number;
|
|
381
|
-
}) {
|
|
382
|
-
return {
|
|
383
|
-
timeoutMs: Math.max(
|
|
384
|
-
1,
|
|
385
|
-
Math.min(AUTOMATION_PLANNER_MAX_TIMEOUT_MS, Math.floor(budget.timeoutMs / 4)),
|
|
386
|
-
),
|
|
387
|
-
maxTurns: Math.max(1, Math.min(AUTOMATION_PLANNER_MAX_TURNS, Math.floor(budget.maxTurns / 4))),
|
|
388
|
-
maxToolCalls: Math.max(
|
|
389
|
-
1,
|
|
390
|
-
Math.min(AUTOMATION_PLANNER_MAX_TOOL_CALLS, Math.floor(budget.maxToolCalls / 4)),
|
|
391
|
-
),
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
function reserveExecutionBudget(
|
|
396
|
-
request: ReturnType<typeof parseAutomationRequest>,
|
|
397
|
-
planner: ReturnType<typeof reservePlannerBudget>,
|
|
398
|
-
maxWorkflowTasks: number,
|
|
399
|
-
): ReturnType<typeof parseAutomationRequest> | undefined {
|
|
400
|
-
const remaining = {
|
|
401
|
-
timeoutMs: request.aggregateBudget.timeoutMs - planner.timeoutMs,
|
|
402
|
-
maxTurns: request.aggregateBudget.maxTurns - planner.maxTurns,
|
|
403
|
-
maxToolCalls: request.aggregateBudget.maxToolCalls - planner.maxToolCalls,
|
|
404
|
-
};
|
|
405
|
-
if (remaining.timeoutMs < 1 || remaining.maxTurns < 1 || remaining.maxToolCalls < 1) {
|
|
406
|
-
return undefined;
|
|
407
|
-
}
|
|
408
|
-
return {
|
|
409
|
-
...request,
|
|
410
|
-
aggregateBudget: {
|
|
411
|
-
...request.aggregateBudget,
|
|
412
|
-
...remaining,
|
|
413
|
-
maxTasks: Math.min(request.aggregateBudget.maxTasks, maxWorkflowTasks),
|
|
414
|
-
},
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
function plannerFailure(result: SingleResult): Error {
|
|
419
|
-
return new Error(
|
|
420
|
-
boundedPrivateText(
|
|
421
|
-
result.errorMessage || result.stderr.trim() || "Automation planner failed",
|
|
422
|
-
2 * 1024,
|
|
423
|
-
),
|
|
424
|
-
);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
function assertCurrent(signal: AbortSignal, isCurrent: () => boolean): void {
|
|
428
|
-
if (signal.aborted || !isCurrent()) throw abortError("Autonomous workflow owner was replaced");
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
function abortError(message: string): Error {
|
|
432
|
-
const error = new Error(message);
|
|
433
|
-
error.name = "AbortError";
|
|
434
|
-
return error;
|
|
435
|
-
}
|