@narumitw/pi-subagents 1.0.0 → 1.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 +12 -4
- package/package.json +1 -1
- package/src/automation-registration.ts +137 -0
- package/src/automation-tool.ts +40 -0
- package/src/automation.ts +6 -156
- package/src/cached-module-loader.ts +18 -0
- package/src/config-registration.ts +89 -0
- package/src/config-ui.ts +2 -2
- package/src/consult-registration.ts +132 -0
- package/src/consult-tool.ts +95 -0
- package/src/consult.ts +37 -204
- package/src/create-stateful-transport.ts +106 -18
- package/src/inspect-registration.ts +64 -0
- package/src/inspect-tool.ts +43 -0
- package/src/inspect.ts +7 -69
- package/src/params.ts +1 -1
- package/src/pi-args.ts +41 -0
- package/src/render.ts +2 -6
- package/src/runner-outcome.ts +31 -0
- package/src/runner.ts +18 -79
- package/src/stateful.ts +6 -1
- package/src/subagents.ts +81 -28
- package/src/verified-execution-contract.ts +3 -31
- package/src/verified-execution-schema.ts +32 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { ExtensionAPI, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { cachedModuleLoader, throwIfAborted } from "./cached-module-loader.js";
|
|
3
|
+
import type { SubagentInspectRuntime } from "./inspect.js";
|
|
4
|
+
import { renderInspectCall, renderInspectResult } from "./inspect-render.js";
|
|
5
|
+
import { SubagentInspectParams } from "./inspect-tool.js";
|
|
6
|
+
|
|
7
|
+
interface InspectExecutionModule {
|
|
8
|
+
executeSubagentInspect: typeof import("./inspect.js").executeSubagentInspect;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface InspectRegistrationDependencies {
|
|
12
|
+
loadExecution?: () => Promise<InspectExecutionModule>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function registerSubagentInspect(
|
|
16
|
+
pi: ExtensionAPI,
|
|
17
|
+
runtime: SubagentInspectRuntime,
|
|
18
|
+
dependencies: InspectRegistrationDependencies = {},
|
|
19
|
+
): void {
|
|
20
|
+
const loadExecution = cachedModuleLoader(
|
|
21
|
+
dependencies.loadExecution ?? (() => import("./inspect.js")),
|
|
22
|
+
);
|
|
23
|
+
let lifecycleGeneration = 0;
|
|
24
|
+
pi.on("session_start", () => {
|
|
25
|
+
lifecycleGeneration += 1;
|
|
26
|
+
});
|
|
27
|
+
pi.on("session_shutdown", () => {
|
|
28
|
+
lifecycleGeneration += 1;
|
|
29
|
+
});
|
|
30
|
+
const definition: ToolDefinition<typeof SubagentInspectParams, Record<string, unknown>> = {
|
|
31
|
+
name: "subagent_inspect",
|
|
32
|
+
label: "Inspect Subagents",
|
|
33
|
+
description:
|
|
34
|
+
"Inspect available subagent definitions, models, retained runs, persisted blocking workflows, runtime status, and diagnostics without changing subagent or workspace state. This tool never starts a child, sends or acknowledges messages, interrupts or closes runs, changes settings, or modifies files.",
|
|
35
|
+
promptSnippet: "Inspect subagent metadata and runtime state without changing it",
|
|
36
|
+
parameters: SubagentInspectParams,
|
|
37
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
38
|
+
const ownerGeneration = lifecycleGeneration;
|
|
39
|
+
throwIfAborted(signal, "Subagent inspection loading was cancelled");
|
|
40
|
+
let executionModule: InspectExecutionModule;
|
|
41
|
+
try {
|
|
42
|
+
executionModule = await loadExecution();
|
|
43
|
+
} catch (error) {
|
|
44
|
+
throwIfAborted(signal, "Subagent inspection loading was cancelled");
|
|
45
|
+
if (ownerGeneration !== lifecycleGeneration) {
|
|
46
|
+
throw new DOMException("Subagent inspection session was replaced", "AbortError");
|
|
47
|
+
}
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
throwIfAborted(signal, "Subagent inspection loading was cancelled");
|
|
51
|
+
if (ownerGeneration !== lifecycleGeneration) {
|
|
52
|
+
throw new DOMException("Subagent inspection session was replaced", "AbortError");
|
|
53
|
+
}
|
|
54
|
+
return executionModule.executeSubagentInspect(params, ctx, runtime);
|
|
55
|
+
},
|
|
56
|
+
renderCall(args, theme) {
|
|
57
|
+
return renderInspectCall(args, theme);
|
|
58
|
+
},
|
|
59
|
+
renderResult(result, options, theme, context) {
|
|
60
|
+
return renderInspectResult(result, options, theme, context);
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
pi.registerTool(definition);
|
|
64
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { StringEnum } from "@earendil-works/pi-ai";
|
|
2
|
+
import { type Static, Type } from "typebox";
|
|
3
|
+
|
|
4
|
+
const INSPECT_ACTIONS = [
|
|
5
|
+
"list_agents",
|
|
6
|
+
"get_agent",
|
|
7
|
+
"list_runs",
|
|
8
|
+
"get_run",
|
|
9
|
+
"list_workflows",
|
|
10
|
+
"get_workflow",
|
|
11
|
+
"list_models",
|
|
12
|
+
"preview_context",
|
|
13
|
+
"status",
|
|
14
|
+
"diagnose",
|
|
15
|
+
] as const;
|
|
16
|
+
|
|
17
|
+
const AgentScopeSchema = StringEnum(["user", "project", "both"] as const, {
|
|
18
|
+
default: "user",
|
|
19
|
+
description: "Agent definition scope. Project scopes require a trusted project.",
|
|
20
|
+
});
|
|
21
|
+
const LimitSchema = Type.Number({ minimum: 1, maximum: 100, multipleOf: 1 });
|
|
22
|
+
const ContextModeSchema = Type.Union([
|
|
23
|
+
StringEnum(["none", "all", "summary"] as const),
|
|
24
|
+
Type.Number({ minimum: 1, multipleOf: 1 }),
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
export const SubagentInspectParams = Type.Object(
|
|
28
|
+
{
|
|
29
|
+
action: StringEnum(INSPECT_ACTIONS),
|
|
30
|
+
agent: Type.Optional(Type.String({ minLength: 1 })),
|
|
31
|
+
agentId: Type.Optional(Type.String({ minLength: 1 })),
|
|
32
|
+
workflowId: Type.Optional(Type.String({ minLength: 1, maxLength: 256 })),
|
|
33
|
+
agentScope: Type.Optional(AgentScopeSchema),
|
|
34
|
+
limit: Type.Optional(LimitSchema),
|
|
35
|
+
includeClosed: Type.Optional(Type.Boolean({ default: false })),
|
|
36
|
+
context: Type.Optional(ContextModeSchema),
|
|
37
|
+
contextEntryIds: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
38
|
+
},
|
|
39
|
+
{ additionalProperties: false },
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
export type SubagentInspectParams = Static<typeof SubagentInspectParams>;
|
|
43
|
+
export { INSPECT_ACTIONS };
|
package/src/inspect.ts
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
CONFIG_DIR_NAME,
|
|
5
|
-
type ExtensionAPI,
|
|
6
|
-
type ExtensionContext,
|
|
7
|
-
} from "@earendil-works/pi-coding-agent";
|
|
8
|
-
import { type Static, Type } from "typebox";
|
|
2
|
+
import { CONFIG_DIR_NAME, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
9
3
|
import { discoverAgents } from "./agents/discovery.js";
|
|
10
4
|
import type {
|
|
11
5
|
AgentConfig,
|
|
@@ -16,7 +10,7 @@ import type {
|
|
|
16
10
|
import { projectCapabilityManifest } from "./capabilities.js";
|
|
17
11
|
import { resolveConsultTools } from "./consult-policy.js";
|
|
18
12
|
import { buildContextSnapshot, type ContextMode } from "./context.js";
|
|
19
|
-
import {
|
|
13
|
+
import { INSPECT_ACTIONS } from "./inspect-tool.js";
|
|
20
14
|
import { DEFAULT_MAX_CONTEXT_BYTES } from "./limits.js";
|
|
21
15
|
import { resolvePiInvocation } from "./pi-invocation.js";
|
|
22
16
|
import type { AgentRunInspectionDetail, AgentRunInspectionSummary } from "./registry.js";
|
|
@@ -36,47 +30,10 @@ import type { StatefulSubagentRuntimeStatus } from "./stateful.js";
|
|
|
36
30
|
import type { WorkItemLedgerSnapshot } from "./work-item-ledger.js";
|
|
37
31
|
import { inspectSessionWorkflows } from "./work-item-persistence.js";
|
|
38
32
|
|
|
39
|
-
const INSPECT_ACTIONS = [
|
|
40
|
-
"list_agents",
|
|
41
|
-
"get_agent",
|
|
42
|
-
"list_runs",
|
|
43
|
-
"get_run",
|
|
44
|
-
"list_workflows",
|
|
45
|
-
"get_workflow",
|
|
46
|
-
"list_models",
|
|
47
|
-
"preview_context",
|
|
48
|
-
"status",
|
|
49
|
-
"diagnose",
|
|
50
|
-
] as const;
|
|
51
|
-
|
|
52
|
-
const AgentScopeSchema = StringEnum(["user", "project", "both"] as const, {
|
|
53
|
-
default: "user",
|
|
54
|
-
description: "Agent definition scope. Project scopes require a trusted project.",
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
const LimitSchema = Type.Number({ minimum: 1, maximum: 100, multipleOf: 1 });
|
|
58
|
-
const ContextModeSchema = Type.Union([
|
|
59
|
-
StringEnum(["none", "all", "summary"] as const),
|
|
60
|
-
Type.Number({ minimum: 1, multipleOf: 1 }),
|
|
61
|
-
]);
|
|
62
33
|
const MAX_DETAILS_LIST_BYTES = 40 * 1024;
|
|
63
34
|
|
|
64
|
-
export
|
|
65
|
-
|
|
66
|
-
action: StringEnum(INSPECT_ACTIONS),
|
|
67
|
-
agent: Type.Optional(Type.String({ minLength: 1 })),
|
|
68
|
-
agentId: Type.Optional(Type.String({ minLength: 1 })),
|
|
69
|
-
workflowId: Type.Optional(Type.String({ minLength: 1, maxLength: 256 })),
|
|
70
|
-
agentScope: Type.Optional(AgentScopeSchema),
|
|
71
|
-
limit: Type.Optional(LimitSchema),
|
|
72
|
-
includeClosed: Type.Optional(Type.Boolean({ default: false })),
|
|
73
|
-
context: Type.Optional(ContextModeSchema),
|
|
74
|
-
contextEntryIds: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
75
|
-
},
|
|
76
|
-
{ additionalProperties: false },
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
export type SubagentInspectParams = Static<typeof SubagentInspectParams>;
|
|
35
|
+
export { registerSubagentInspect } from "./inspect-registration.js";
|
|
36
|
+
export { SubagentInspectParams } from "./inspect-tool.js";
|
|
80
37
|
|
|
81
38
|
export interface SubagentInspectRuntime {
|
|
82
39
|
getBlockingEnabled(): boolean;
|
|
@@ -106,26 +63,6 @@ type ValidatedInspectOperation =
|
|
|
106
63
|
| { action: "status" }
|
|
107
64
|
| { action: "diagnose" };
|
|
108
65
|
|
|
109
|
-
export function registerSubagentInspect(pi: ExtensionAPI, runtime: SubagentInspectRuntime): void {
|
|
110
|
-
pi.registerTool({
|
|
111
|
-
name: "subagent_inspect",
|
|
112
|
-
label: "Inspect Subagents",
|
|
113
|
-
description:
|
|
114
|
-
"Inspect available subagent definitions, models, retained runs, persisted blocking workflows, runtime status, and diagnostics without changing subagent or workspace state. This tool never starts a child, sends or acknowledges messages, interrupts or closes runs, changes settings, or modifies files.",
|
|
115
|
-
promptSnippet: "Inspect subagent metadata and runtime state without changing it",
|
|
116
|
-
parameters: SubagentInspectParams,
|
|
117
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx): Promise<InspectToolResult> {
|
|
118
|
-
return executeSubagentInspect(validateInspectParams(params), ctx, runtime);
|
|
119
|
-
},
|
|
120
|
-
renderCall(args, theme) {
|
|
121
|
-
return renderInspectCall(args, theme);
|
|
122
|
-
},
|
|
123
|
-
renderResult(result, options, theme, context) {
|
|
124
|
-
return renderInspectResult(result, options, theme, context);
|
|
125
|
-
},
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
66
|
export function validateInspectParams(params: unknown): ValidatedInspectOperation {
|
|
130
67
|
const values = parameterRecord(params);
|
|
131
68
|
const rawAction = values.action;
|
|
@@ -195,11 +132,12 @@ export function validateInspectParams(params: unknown): ValidatedInspectOperatio
|
|
|
195
132
|
return { action };
|
|
196
133
|
}
|
|
197
134
|
|
|
198
|
-
async function executeSubagentInspect(
|
|
199
|
-
|
|
135
|
+
export async function executeSubagentInspect(
|
|
136
|
+
params: unknown,
|
|
200
137
|
ctx: ExtensionContext,
|
|
201
138
|
runtime: SubagentInspectRuntime,
|
|
202
139
|
): Promise<InspectToolResult> {
|
|
140
|
+
const operation = validateInspectParams(params);
|
|
203
141
|
if (operation.action === "list_agents" || operation.action === "get_agent") {
|
|
204
142
|
assertTrustedScope(operation.agentScope, ctx);
|
|
205
143
|
const settings = inspectSubagentSettings().settings;
|
package/src/params.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { MAX_CONFIGURABLE_PARALLEL_TASKS, MAX_SUBAGENT_TIMEOUT_MS } from "./limi
|
|
|
6
6
|
import { PANEL_PRESETS } from "./panel-planning.js";
|
|
7
7
|
import { SUBAGENT_RESULT_FORMATS } from "./result-contract.js";
|
|
8
8
|
import { MAX_SUBAGENT_TOOL_CALLS, MAX_SUBAGENT_TURNS } from "./turn-budget.js";
|
|
9
|
-
import { VerifiedExecutionContractSchema } from "./verified-execution-
|
|
9
|
+
import { VerifiedExecutionContractSchema } from "./verified-execution-schema.js";
|
|
10
10
|
|
|
11
11
|
const TimeoutMs = Type.Number({
|
|
12
12
|
description:
|
package/src/pi-args.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { SubagentThinkingLevel } from "./agents/types.js";
|
|
2
|
+
|
|
3
|
+
export interface PiArgsOptions {
|
|
4
|
+
model?: string;
|
|
5
|
+
thinkingLevel?: SubagentThinkingLevel;
|
|
6
|
+
tools?: string[];
|
|
7
|
+
disableExtensions?: boolean;
|
|
8
|
+
disableSkills?: boolean;
|
|
9
|
+
disablePromptTemplates?: boolean;
|
|
10
|
+
disableContextFiles?: boolean;
|
|
11
|
+
projectTrust?: boolean;
|
|
12
|
+
baseSystemPromptPath?: string;
|
|
13
|
+
appendSystemPromptPaths?: string[];
|
|
14
|
+
/** Existing single append prompt path retained for compatibility. */
|
|
15
|
+
systemPromptPath?: string;
|
|
16
|
+
task: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function buildPiArgs(options: PiArgsOptions): string[] {
|
|
20
|
+
const args: string[] = ["--mode", "json", "-p", "--no-session"];
|
|
21
|
+
if (options.model) args.push("--model", options.model);
|
|
22
|
+
if (options.thinkingLevel) args.push("--thinking", options.thinkingLevel);
|
|
23
|
+
if (options.disableExtensions) args.push("--no-extensions");
|
|
24
|
+
if (options.disableSkills) args.push("--no-skills");
|
|
25
|
+
if (options.disablePromptTemplates) args.push("--no-prompt-templates");
|
|
26
|
+
if (options.disableContextFiles) args.push("--no-context-files");
|
|
27
|
+
if (options.projectTrust !== undefined) {
|
|
28
|
+
args.push(options.projectTrust ? "--approve" : "--no-approve");
|
|
29
|
+
}
|
|
30
|
+
if (Array.isArray(options.tools)) {
|
|
31
|
+
if (options.tools.length > 0) args.push("--tools", options.tools.join(","));
|
|
32
|
+
else args.push("--no-tools");
|
|
33
|
+
}
|
|
34
|
+
if (options.baseSystemPromptPath) args.push("--system-prompt", options.baseSystemPromptPath);
|
|
35
|
+
for (const promptPath of options.appendSystemPromptPaths ?? []) {
|
|
36
|
+
args.push("--append-system-prompt", promptPath);
|
|
37
|
+
}
|
|
38
|
+
if (options.systemPromptPath) args.push("--append-system-prompt", options.systemPromptPath);
|
|
39
|
+
args.push(`Task: ${options.task}`);
|
|
40
|
+
return args;
|
|
41
|
+
}
|
package/src/render.ts
CHANGED
|
@@ -11,12 +11,8 @@ import type { AgentScope, SubagentThinkingLevel } from "./agents/types.js";
|
|
|
11
11
|
import { renderPanelCall, renderPanelResult } from "./panel-render.js";
|
|
12
12
|
import { hasUsableAggregator, type SubagentParams } from "./params.js";
|
|
13
13
|
import { expansionHint, formatToolActivity, safeBlock, safeLine } from "./render-common.js";
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
isResultError,
|
|
17
|
-
type SingleResult,
|
|
18
|
-
type SubagentDetails,
|
|
19
|
-
} from "./runner.js";
|
|
14
|
+
import type { SingleResult, SubagentDetails } from "./runner.js";
|
|
15
|
+
import { getResultFinalOutput, isResultError } from "./runner-outcome.js";
|
|
20
16
|
|
|
21
17
|
const COLLAPSED_ITEM_COUNT = 5;
|
|
22
18
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { SingleResult } from "./runner.js";
|
|
2
|
+
import {
|
|
3
|
+
formatResultFailure as formatBaseResultFailure,
|
|
4
|
+
getResultFinalOutput as getBaseResultFinalOutput,
|
|
5
|
+
isResultError as isBaseResultError,
|
|
6
|
+
} from "./runner-result.js";
|
|
7
|
+
|
|
8
|
+
export function getResultFinalOutput(result: SingleResult): string {
|
|
9
|
+
return getBaseResultFinalOutput(result);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function isResultError(result: SingleResult): boolean {
|
|
13
|
+
return (
|
|
14
|
+
isBaseResultError(result) ||
|
|
15
|
+
result.resultContractInvalid === true ||
|
|
16
|
+
(result.outcome !== undefined &&
|
|
17
|
+
result.outcome.status !== "completed" &&
|
|
18
|
+
result.outcome.status !== "partial")
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function formatResultFailure(result: SingleResult): string {
|
|
23
|
+
const contractError = result.resultContractInvalid
|
|
24
|
+
? `Subagent returned an invalid ${result.resultFormat ?? "structured"} result contract`
|
|
25
|
+
: result.outcome && !["completed", "partial"].includes(result.outcome.status)
|
|
26
|
+
? `Subagent outcome ${result.outcome.status}${result.outcome.reasonCode ? ` (${result.outcome.reasonCode})` : ""}; recovery: ${result.outcome.recoveryActions.join(", ") || "none"}`
|
|
27
|
+
: undefined;
|
|
28
|
+
return contractError
|
|
29
|
+
? formatBaseResultFailure({ ...result, errorMessage: contractError })
|
|
30
|
+
: formatBaseResultFailure(result);
|
|
31
|
+
}
|
package/src/runner.ts
CHANGED
|
@@ -31,6 +31,7 @@ import type { PanelSynthesis } from "./panel-contract.js";
|
|
|
31
31
|
import type { PanelEvidenceArtifact } from "./panel-evidence.js";
|
|
32
32
|
import type { PanelFailure } from "./panel-failure.js";
|
|
33
33
|
import type { PanelPhaseBudgets, PanelPreset } from "./panel-planning.js";
|
|
34
|
+
import { buildPiArgs } from "./pi-args.js";
|
|
34
35
|
import { resolvePiInvocation } from "./pi-invocation.js";
|
|
35
36
|
import { JsonLineDecoder } from "./protocol.js";
|
|
36
37
|
import {
|
|
@@ -38,12 +39,8 @@ import {
|
|
|
38
39
|
parseAnyStructuredSubagentResult,
|
|
39
40
|
type SubagentResultFormat,
|
|
40
41
|
} from "./result-contract.js";
|
|
41
|
-
import {
|
|
42
|
-
|
|
43
|
-
getResultFinalOutput as getBaseResultFinalOutput,
|
|
44
|
-
getFinalOutput,
|
|
45
|
-
isResultError as isBaseResultError,
|
|
46
|
-
} from "./runner-result.js";
|
|
42
|
+
import { formatResultFailure, getResultFinalOutput, isResultError } from "./runner-outcome.js";
|
|
43
|
+
import { getFinalOutput } from "./runner-result.js";
|
|
47
44
|
import {
|
|
48
45
|
addUsageValue,
|
|
49
46
|
mergeUsageStats,
|
|
@@ -68,6 +65,13 @@ import type { WorkItemLedgerSnapshot } from "./work-item-ledger.js";
|
|
|
68
65
|
|
|
69
66
|
export const KILL_GRACE_MS = 5000;
|
|
70
67
|
|
|
68
|
+
export type { PiArgsOptions } from "./pi-args.js";
|
|
69
|
+
export { buildPiArgs } from "./pi-args.js";
|
|
70
|
+
export {
|
|
71
|
+
formatResultFailure,
|
|
72
|
+
getResultFinalOutput,
|
|
73
|
+
isResultError,
|
|
74
|
+
} from "./runner-outcome.js";
|
|
71
75
|
export type { UsageStats } from "./runner-usage.js";
|
|
72
76
|
export type RecentActivityItem =
|
|
73
77
|
| { type: "text"; text: string }
|
|
@@ -154,31 +158,6 @@ export interface SubagentDetails {
|
|
|
154
158
|
isError?: boolean;
|
|
155
159
|
}
|
|
156
160
|
|
|
157
|
-
export function getResultFinalOutput(result: SingleResult): string {
|
|
158
|
-
return getBaseResultFinalOutput(result);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export function isResultError(result: SingleResult): boolean {
|
|
162
|
-
return (
|
|
163
|
-
isBaseResultError(result) ||
|
|
164
|
-
result.resultContractInvalid === true ||
|
|
165
|
-
(result.outcome !== undefined &&
|
|
166
|
-
result.outcome.status !== "completed" &&
|
|
167
|
-
result.outcome.status !== "partial")
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export function formatResultFailure(result: SingleResult): string {
|
|
172
|
-
const contractError = result.resultContractInvalid
|
|
173
|
-
? `Subagent returned an invalid ${result.resultFormat ?? "structured"} result contract`
|
|
174
|
-
: result.outcome && !["completed", "partial"].includes(result.outcome.status)
|
|
175
|
-
? `Subagent outcome ${result.outcome.status}${result.outcome.reasonCode ? ` (${result.outcome.reasonCode})` : ""}; recovery: ${result.outcome.recoveryActions.join(", ") || "none"}`
|
|
176
|
-
: undefined;
|
|
177
|
-
return contractError
|
|
178
|
-
? formatBaseResultFailure({ ...result, errorMessage: contractError })
|
|
179
|
-
: formatBaseResultFailure(result);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
161
|
function boundMessageText(
|
|
183
162
|
message: Message,
|
|
184
163
|
maxBytes: number,
|
|
@@ -359,46 +338,6 @@ async function writePromptToTempFile(
|
|
|
359
338
|
return { dir: tmpDir, filePath };
|
|
360
339
|
}
|
|
361
340
|
|
|
362
|
-
export interface PiArgsOptions {
|
|
363
|
-
model?: string;
|
|
364
|
-
thinkingLevel?: SubagentThinkingLevel;
|
|
365
|
-
tools?: string[];
|
|
366
|
-
disableExtensions?: boolean;
|
|
367
|
-
disableSkills?: boolean;
|
|
368
|
-
disablePromptTemplates?: boolean;
|
|
369
|
-
disableContextFiles?: boolean;
|
|
370
|
-
projectTrust?: boolean;
|
|
371
|
-
baseSystemPromptPath?: string;
|
|
372
|
-
appendSystemPromptPaths?: string[];
|
|
373
|
-
/** Existing single append prompt path retained for compatibility. */
|
|
374
|
-
systemPromptPath?: string;
|
|
375
|
-
task: string;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
export function buildPiArgs(options: PiArgsOptions): string[] {
|
|
379
|
-
const args: string[] = ["--mode", "json", "-p", "--no-session"];
|
|
380
|
-
if (options.model) args.push("--model", options.model);
|
|
381
|
-
if (options.thinkingLevel) args.push("--thinking", options.thinkingLevel);
|
|
382
|
-
if (options.disableExtensions) args.push("--no-extensions");
|
|
383
|
-
if (options.disableSkills) args.push("--no-skills");
|
|
384
|
-
if (options.disablePromptTemplates) args.push("--no-prompt-templates");
|
|
385
|
-
if (options.disableContextFiles) args.push("--no-context-files");
|
|
386
|
-
if (options.projectTrust !== undefined) {
|
|
387
|
-
args.push(options.projectTrust ? "--approve" : "--no-approve");
|
|
388
|
-
}
|
|
389
|
-
if (Array.isArray(options.tools)) {
|
|
390
|
-
if (options.tools.length > 0) args.push("--tools", options.tools.join(","));
|
|
391
|
-
else args.push("--no-tools");
|
|
392
|
-
}
|
|
393
|
-
if (options.baseSystemPromptPath) args.push("--system-prompt", options.baseSystemPromptPath);
|
|
394
|
-
for (const promptPath of options.appendSystemPromptPaths ?? []) {
|
|
395
|
-
args.push("--append-system-prompt", promptPath);
|
|
396
|
-
}
|
|
397
|
-
if (options.systemPromptPath) args.push("--append-system-prompt", options.systemPromptPath);
|
|
398
|
-
args.push(`Task: ${options.task}`);
|
|
399
|
-
return args;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
341
|
function signalProcess(proc: ReturnType<typeof spawn>, signal: NodeJS.Signals): void {
|
|
403
342
|
if (process.platform !== "win32" && proc.pid) {
|
|
404
343
|
try {
|
|
@@ -857,16 +796,16 @@ export async function runSingleAgent(
|
|
|
857
796
|
onExceeded: stopForBudget,
|
|
858
797
|
});
|
|
859
798
|
|
|
860
|
-
timeout = setTimeout(() => {
|
|
861
|
-
stopForBudget({
|
|
862
|
-
reason: launchPolicy?.workTimeoutReason ?? "work_timeout",
|
|
863
|
-
limit: launchPolicy?.workTimeoutReportLimit ?? timeoutMs,
|
|
864
|
-
});
|
|
865
|
-
}, timeoutMs);
|
|
866
|
-
timeout.unref();
|
|
867
|
-
|
|
868
799
|
proc.once("spawn", () => {
|
|
869
800
|
currentResult.processStarted = true;
|
|
801
|
+
if (settled || budgetStop || wasAborted) return;
|
|
802
|
+
timeout = setTimeout(() => {
|
|
803
|
+
stopForBudget({
|
|
804
|
+
reason: launchPolicy?.workTimeoutReason ?? "work_timeout",
|
|
805
|
+
limit: launchPolicy?.workTimeoutReportLimit ?? timeoutMs,
|
|
806
|
+
});
|
|
807
|
+
}, timeoutMs);
|
|
808
|
+
timeout.unref();
|
|
870
809
|
});
|
|
871
810
|
proc.stdout?.on("data", (data) => decoder.push(data));
|
|
872
811
|
proc.stderr?.on("data", (data) => {
|
package/src/stateful.ts
CHANGED
|
@@ -20,7 +20,10 @@ import {
|
|
|
20
20
|
import { issueCapabilityGrant } from "./capability-grant.js";
|
|
21
21
|
import { CompletionDeliveryBroker } from "./completion-delivery.js";
|
|
22
22
|
import { buildContextSnapshot, type ContextMode, redactPrivateText } from "./context.js";
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
type CreateStatefulTransportOptions,
|
|
25
|
+
createStatefulTransport,
|
|
26
|
+
} from "./create-stateful-transport.js";
|
|
24
27
|
import {
|
|
25
28
|
assertDelegationTargetAllowed,
|
|
26
29
|
resolveSubagentTarget,
|
|
@@ -133,6 +136,7 @@ export interface StatefulSubagentDependencies {
|
|
|
133
136
|
workspaceManager?: WorkspaceManager;
|
|
134
137
|
settings?: SubagentRuntimeSettings;
|
|
135
138
|
getSettings?: () => SubagentSettings | undefined;
|
|
139
|
+
loadTransport?: CreateStatefulTransportOptions["loadTransport"];
|
|
136
140
|
}
|
|
137
141
|
|
|
138
142
|
export interface StatefulSubagentRuntimeStatus {
|
|
@@ -319,6 +323,7 @@ export function registerStatefulSubagents(
|
|
|
319
323
|
getParentRuntime: () => ({ ...parentRuntime }),
|
|
320
324
|
getSettings: getCurrentSettings,
|
|
321
325
|
createInProcessSession: dependencies.createInProcessSession,
|
|
326
|
+
loadTransport: dependencies.loadTransport,
|
|
322
327
|
});
|
|
323
328
|
nextRegistry = new AgentRegistry(transport, {
|
|
324
329
|
maxAgents: nextLimits.maxAgents,
|
package/src/subagents.ts
CHANGED
|
@@ -21,11 +21,24 @@ import type {
|
|
|
21
21
|
DelegationCwdPolicy,
|
|
22
22
|
SubagentSettings,
|
|
23
23
|
} from "./agents/types.js";
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
type AutomationRegistrationDependencies,
|
|
26
|
+
registerSubagentAutomation,
|
|
27
|
+
} from "./automation-registration.js";
|
|
28
|
+
import { cachedModuleLoader, throwIfAborted } from "./cached-module-loader.js";
|
|
29
|
+
import {
|
|
30
|
+
type ConfigRegistrationDependencies,
|
|
31
|
+
registerSubagentConfigCommand,
|
|
32
|
+
registerSubagentConfigLifecycle,
|
|
33
|
+
} from "./config-registration.js";
|
|
34
|
+
import {
|
|
35
|
+
type ConsultRegistrationDependencies,
|
|
36
|
+
registerSubagentConsult,
|
|
37
|
+
} from "./consult-registration.js";
|
|
38
|
+
import {
|
|
39
|
+
type InspectRegistrationDependencies,
|
|
40
|
+
registerSubagentInspect,
|
|
41
|
+
} from "./inspect-registration.js";
|
|
29
42
|
import { MAX_BLOCKING_PARALLEL_CONCURRENCY } from "./limits.js";
|
|
30
43
|
import { SubagentParams } from "./params.js";
|
|
31
44
|
import { renderSubagentCall, renderSubagentResult } from "./render.js";
|
|
@@ -40,17 +53,34 @@ import {
|
|
|
40
53
|
resolveBlockingMaxParallelTasks,
|
|
41
54
|
} from "./settings.js";
|
|
42
55
|
import { registerStatefulSubagents } from "./stateful.js";
|
|
56
|
+
import type { SubagentTransport } from "./transport.js";
|
|
57
|
+
|
|
58
|
+
type BlockingExecutionModule = Pick<typeof import("./execution.js"), "executeSubagent">;
|
|
59
|
+
|
|
60
|
+
export interface SubagentsDependencies {
|
|
61
|
+
loadBlockingExecution?: () => Promise<BlockingExecutionModule>;
|
|
62
|
+
loadStatefulTransport?: () => Promise<SubagentTransport>;
|
|
63
|
+
automation?: AutomationRegistrationDependencies;
|
|
64
|
+
config?: ConfigRegistrationDependencies;
|
|
65
|
+
consult?: ConsultRegistrationDependencies;
|
|
66
|
+
inspect?: InspectRegistrationDependencies;
|
|
67
|
+
}
|
|
43
68
|
|
|
44
|
-
export default function (pi: ExtensionAPI) {
|
|
69
|
+
export default function (pi: ExtensionAPI, dependencies: SubagentsDependencies = {}) {
|
|
70
|
+
const loadBlockingExecution = cachedModuleLoader(
|
|
71
|
+
dependencies.loadBlockingExecution ?? (() => import("./execution.js")),
|
|
72
|
+
);
|
|
45
73
|
const configOwner = registerSubagentConfigLifecycle(pi);
|
|
46
74
|
const settings = readSubagentSettings();
|
|
47
75
|
let currentSettings: SubagentSettings | undefined = settings;
|
|
48
76
|
let currentCatalog = "";
|
|
49
77
|
const blockingEnabled = settings?.blocking?.enabled !== false;
|
|
50
78
|
const refreshBlockingCatalog = blockingEnabled
|
|
51
|
-
? registerBlockingSubagent(pi, () => currentSettings)
|
|
79
|
+
? registerBlockingSubagent(pi, () => currentSettings, loadBlockingExecution)
|
|
52
80
|
: () => undefined;
|
|
53
|
-
if (blockingEnabled)
|
|
81
|
+
if (blockingEnabled) {
|
|
82
|
+
registerSubagentAutomation(pi, { getSettings: () => currentSettings }, dependencies.automation);
|
|
83
|
+
}
|
|
54
84
|
let refreshStatefulCatalog: (catalog: string) => void = () => undefined;
|
|
55
85
|
let refreshConsultCatalog: (catalog: string) => void = () => undefined;
|
|
56
86
|
|
|
@@ -78,6 +108,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
78
108
|
blockingEnabled,
|
|
79
109
|
settings: settings?.stateful,
|
|
80
110
|
getSettings: () => currentSettings,
|
|
111
|
+
loadTransport: dependencies.loadStatefulTransport,
|
|
81
112
|
});
|
|
82
113
|
refreshStatefulCatalog = statefulRuntime.setAgentCatalog;
|
|
83
114
|
const getBlockingEnabled = () => blockingEnabled;
|
|
@@ -88,18 +119,24 @@ export default function (pi: ExtensionAPI) {
|
|
|
88
119
|
currentSettings?.cwdPolicy?.consultation ?? DEFAULT_CONSULTATION_CWD_POLICY;
|
|
89
120
|
const getDelegationCwdPolicy = () =>
|
|
90
121
|
currentSettings?.cwdPolicy?.delegation ?? DEFAULT_DELEGATION_CWD_POLICY;
|
|
91
|
-
registerSubagentInspect(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
122
|
+
registerSubagentInspect(
|
|
123
|
+
pi,
|
|
124
|
+
{
|
|
125
|
+
...statefulRuntime,
|
|
126
|
+
getBlockingEnabled,
|
|
127
|
+
getMaxParallelTasks,
|
|
128
|
+
getConsultResourcePolicy,
|
|
129
|
+
getConsultationCwdPolicy,
|
|
130
|
+
getDelegationCwdPolicy,
|
|
131
|
+
},
|
|
132
|
+
dependencies.inspect,
|
|
133
|
+
);
|
|
99
134
|
if (blockingEnabled) {
|
|
100
|
-
refreshConsultCatalog = registerSubagentConsult(
|
|
101
|
-
|
|
102
|
-
|
|
135
|
+
refreshConsultCatalog = registerSubagentConsult(
|
|
136
|
+
pi,
|
|
137
|
+
{ getSettings: () => currentSettings },
|
|
138
|
+
dependencies.consult,
|
|
139
|
+
);
|
|
103
140
|
}
|
|
104
141
|
registerSubagentConfigCommand(
|
|
105
142
|
pi,
|
|
@@ -155,12 +192,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
155
192
|
},
|
|
156
193
|
},
|
|
157
194
|
configOwner,
|
|
195
|
+
dependencies.config,
|
|
158
196
|
);
|
|
159
197
|
}
|
|
160
198
|
|
|
161
199
|
function registerBlockingSubagent(
|
|
162
200
|
pi: ExtensionAPI,
|
|
163
201
|
getSettings: () => SubagentSettings | undefined,
|
|
202
|
+
loadExecution: () => Promise<BlockingExecutionModule>,
|
|
164
203
|
): (catalog: string) => void {
|
|
165
204
|
let catalog = "";
|
|
166
205
|
const activeControllers = new Set<AbortController>();
|
|
@@ -212,14 +251,28 @@ function registerBlockingSubagent(
|
|
|
212
251
|
const effectiveSignal = signal
|
|
213
252
|
? AbortSignal.any([signal, lifecycleController.signal])
|
|
214
253
|
: lifecycleController.signal;
|
|
215
|
-
const work =
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
254
|
+
const work = (async () => {
|
|
255
|
+
throwIfAborted(effectiveSignal, "Blocking subagent execution was cancelled");
|
|
256
|
+
let executionModule: BlockingExecutionModule;
|
|
257
|
+
try {
|
|
258
|
+
executionModule = await loadExecution();
|
|
259
|
+
} catch (error) {
|
|
260
|
+
throwIfAborted(
|
|
261
|
+
effectiveSignal,
|
|
262
|
+
"Blocking subagent execution was cancelled while loading",
|
|
263
|
+
);
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
266
|
+
throwIfAborted(effectiveSignal, "Blocking subagent execution was cancelled while loading");
|
|
267
|
+
return executionModule.executeSubagent(
|
|
268
|
+
toolCallId,
|
|
269
|
+
params,
|
|
270
|
+
effectiveSignal,
|
|
271
|
+
onUpdate,
|
|
272
|
+
ctx,
|
|
273
|
+
getSettings(),
|
|
274
|
+
);
|
|
275
|
+
})();
|
|
223
276
|
activeWork.add(work);
|
|
224
277
|
try {
|
|
225
278
|
return await work;
|
|
@@ -256,8 +309,8 @@ function appendAgentCatalog(baseDescription: string, catalog: string): string {
|
|
|
256
309
|
}
|
|
257
310
|
|
|
258
311
|
export { parsePositiveInteger } from "./execution/runtime-policy.js";
|
|
312
|
+
export { buildPiArgs } from "./pi-args.js";
|
|
259
313
|
export { formatTokens, formatUsageStats } from "./render.js";
|
|
260
|
-
export { buildPiArgs } from "./runner.js";
|
|
261
314
|
export {
|
|
262
315
|
DEFAULT_CONSULT_RESOURCE_POLICY,
|
|
263
316
|
DEFAULT_CONSULTATION_CWD_POLICY,
|