pi-subagents 0.36.0 → 0.37.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 +17 -0
- package/README.md +93 -8
- package/agents/delegate.md +2 -0
- package/agents/worker.md +2 -0
- package/package.json +4 -2
- package/skills/pi-subagents/SKILL.md +20 -4
- package/src/agents/agent-management.ts +7 -1
- package/src/agents/agents.ts +99 -12
- package/src/api/capability-ceiling.ts +17 -0
- package/src/api/delegation.ts +3 -1
- package/src/api/preflight.ts +399 -0
- package/src/extension/index.ts +2 -0
- package/src/extension/rpc.ts +4 -0
- package/src/extension/schemas.ts +8 -2
- package/src/extension/tool-description.ts +2 -0
- package/src/runs/background/async-execution.ts +125 -14
- package/src/runs/background/async-resume.ts +24 -7
- package/src/runs/background/async-status.ts +18 -0
- package/src/runs/background/process-terminal.ts +280 -0
- package/src/runs/background/run-status.ts +7 -1
- package/src/runs/background/scheduled-runs.ts +6 -1
- package/src/runs/background/stale-run-reconciler.ts +6 -0
- package/src/runs/background/subagent-runner.ts +182 -12
- package/src/runs/foreground/chain-execution.ts +5 -0
- package/src/runs/foreground/execution.ts +59 -13
- package/src/runs/foreground/subagent-executor.ts +32 -2
- package/src/runs/shared/acceptance.ts +41 -30
- package/src/runs/shared/capability-ceiling.ts +177 -0
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +12 -6
- package/src/runs/shared/nested-events.ts +8 -1
- package/src/runs/shared/parallel-utils.ts +5 -0
- package/src/runs/shared/pi-args.ts +141 -58
- package/src/runs/shared/session-lease.ts +25 -5
- package/src/runs/shared/subagent-prompt-runtime.ts +14 -1
- package/src/runs/shared/tool-availability.ts +18 -2
- package/src/shared/launch-contract.ts +123 -0
- package/src/shared/types.ts +104 -7
- package/src/shared/utils.ts +17 -42
- package/src/slash/delegation-adapters.ts +1 -1
- package/src/slash/slash-commands.ts +1 -1
|
@@ -7,6 +7,7 @@ import { SUBAGENT_CHILD_AGENT_ENV, SUBAGENT_CHILD_INDEX_ENV, SUBAGENT_FANOUT_CHI
|
|
|
7
7
|
import { createStructuredOutputToolParameters, STRUCTURED_OUTPUT_CAPTURE_ENV, STRUCTURED_OUTPUT_SCHEMA_ENV, validateStructuredOutputValue } from "./structured-output.ts";
|
|
8
8
|
import {
|
|
9
9
|
CHILD_TOOL_DIAGNOSTIC_PATH_ENV,
|
|
10
|
+
MCP_DIRECT_CHILD_TOOLS_ENV,
|
|
10
11
|
REQUIRED_CHILD_TOOLS_ENV,
|
|
11
12
|
writeChildToolDiagnostic,
|
|
12
13
|
type ChildToolDiagnostic,
|
|
@@ -78,12 +79,24 @@ function readRequiredChildTools(): string[] | undefined {
|
|
|
78
79
|
return required;
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
function readMcpDirectChildTools(): string[] | undefined {
|
|
83
|
+
const encoded = process.env[MCP_DIRECT_CHILD_TOOLS_ENV]?.trim();
|
|
84
|
+
if (!encoded) return undefined;
|
|
85
|
+
try {
|
|
86
|
+
const tools = JSON.parse(encoded) as unknown;
|
|
87
|
+
if (!Array.isArray(tools) || tools.some((name) => typeof name !== "string" || !name)) return undefined;
|
|
88
|
+
return tools;
|
|
89
|
+
} catch {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
81
94
|
function refreshChildToolDiagnostic(pi: ExtensionAPI): ChildToolDiagnostic | undefined {
|
|
82
95
|
const filePath = process.env[CHILD_TOOL_DIAGNOSTIC_PATH_ENV]?.trim();
|
|
83
96
|
const required = readRequiredChildTools();
|
|
84
97
|
if (!filePath || !required) return undefined;
|
|
85
98
|
const available = pi.getAllTools().map((tool) => tool.name);
|
|
86
|
-
return writeChildToolDiagnostic(filePath, required, available, process.env[SUBAGENT_CHILD_AGENT_ENV]?.trim());
|
|
99
|
+
return writeChildToolDiagnostic(filePath, required, available, process.env[SUBAGENT_CHILD_AGENT_ENV]?.trim(), readMcpDirectChildTools());
|
|
87
100
|
}
|
|
88
101
|
|
|
89
102
|
function findSectionEnd(prompt: string, startIndex: number, nextHeaders: string[]): number {
|
|
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
|
|
4
4
|
export const REQUIRED_CHILD_TOOLS_ENV = "PI_SUBAGENT_REQUIRED_TOOLS";
|
|
5
|
+
export const MCP_DIRECT_CHILD_TOOLS_ENV = "PI_SUBAGENT_MCP_DIRECT_TOOLS";
|
|
5
6
|
export const CHILD_TOOL_DIAGNOSTIC_PATH_ENV = "PI_SUBAGENT_TOOL_DIAGNOSTIC_PATH";
|
|
6
7
|
|
|
7
8
|
export interface ChildToolDiagnostic {
|
|
@@ -9,6 +10,7 @@ export interface ChildToolDiagnostic {
|
|
|
9
10
|
required: string[];
|
|
10
11
|
available: string[];
|
|
11
12
|
missing: string[];
|
|
13
|
+
missingMcpDirectTools?: string[];
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
const PI_CORE_CHILD_TOOLS = new Set(["bash", "edit", "find", "grep", "ls", "read", "write"]);
|
|
@@ -18,6 +20,7 @@ export function writeChildToolDiagnostic(
|
|
|
18
20
|
required: string[],
|
|
19
21
|
available: string[],
|
|
20
22
|
agent?: string,
|
|
23
|
+
mcpDirectTools?: string[],
|
|
21
24
|
): ChildToolDiagnostic | undefined {
|
|
22
25
|
const availableNames = new Set([...available, ...PI_CORE_CHILD_TOOLS]);
|
|
23
26
|
const missing = required.filter((name) => !availableNames.has(name));
|
|
@@ -26,7 +29,16 @@ export function writeChildToolDiagnostic(
|
|
|
26
29
|
return undefined;
|
|
27
30
|
}
|
|
28
31
|
|
|
29
|
-
const
|
|
32
|
+
const missingMcpDirectTools = mcpDirectTools?.length
|
|
33
|
+
? missing.filter((name) => mcpDirectTools.includes(name))
|
|
34
|
+
: [];
|
|
35
|
+
const diagnostic: ChildToolDiagnostic = {
|
|
36
|
+
agent,
|
|
37
|
+
required,
|
|
38
|
+
available,
|
|
39
|
+
missing,
|
|
40
|
+
...(missingMcpDirectTools.length > 0 ? { missingMcpDirectTools } : {}),
|
|
41
|
+
};
|
|
30
42
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
31
43
|
fs.writeFileSync(filePath, JSON.stringify(diagnostic), { mode: 0o600 });
|
|
32
44
|
return diagnostic;
|
|
@@ -36,7 +48,7 @@ export function readChildToolDiagnostic(filePath: string | undefined): ChildTool
|
|
|
36
48
|
if (!filePath || !fs.existsSync(filePath)) return undefined;
|
|
37
49
|
const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8")) as Partial<ChildToolDiagnostic>;
|
|
38
50
|
const stringArray = (value: unknown): value is string[] => Array.isArray(value) && value.every((entry) => typeof entry === "string" && entry.length > 0);
|
|
39
|
-
if (!stringArray(parsed.required) || !stringArray(parsed.available) || !stringArray(parsed.missing) || (parsed.agent !== undefined && typeof parsed.agent !== "string")) {
|
|
51
|
+
if (!stringArray(parsed.required) || !stringArray(parsed.available) || !stringArray(parsed.missing) || (parsed.agent !== undefined && typeof parsed.agent !== "string") || (parsed.missingMcpDirectTools !== undefined && !stringArray(parsed.missingMcpDirectTools))) {
|
|
40
52
|
throw new Error(`Malformed child tool diagnostic at '${filePath}'.`);
|
|
41
53
|
}
|
|
42
54
|
return {
|
|
@@ -44,6 +56,7 @@ export function readChildToolDiagnostic(filePath: string | undefined): ChildTool
|
|
|
44
56
|
required: parsed.required,
|
|
45
57
|
available: parsed.available,
|
|
46
58
|
missing: parsed.missing,
|
|
59
|
+
...(parsed.missingMcpDirectTools ? { missingMcpDirectTools: parsed.missingMcpDirectTools } : {}),
|
|
47
60
|
};
|
|
48
61
|
}
|
|
49
62
|
|
|
@@ -52,6 +65,9 @@ export function formatChildToolDiagnostic(diagnostic: ChildToolDiagnostic): stri
|
|
|
52
65
|
return [
|
|
53
66
|
`${subject} requested unavailable child tools: ${diagnostic.missing.join(", ")}.`,
|
|
54
67
|
"The `tools` field is a strict allowlist; it does not load extension code.",
|
|
68
|
+
...(diagnostic.missingMcpDirectTools?.length
|
|
69
|
+
? [`Resolved MCP direct tools missing from the child registry: ${diagnostic.missingMcpDirectTools.join(", ")}. This indicates a host/pi-mcp-adapter registration problem, not a tool-call failure.`]
|
|
70
|
+
: []),
|
|
55
71
|
"For extension tools, add the provider path to `subagentOnlyExtensions` (child-only), `extensions`, or as a path-like entry in `tools`, while keeping each registered tool name in `tools`.",
|
|
56
72
|
"For MCP tools, verify the MCP adapter configuration and selected tool names. For builtin tools, verify the name against the installed Pi version.",
|
|
57
73
|
].join("\n");
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import type { AgentConfig } from "../agents/agents.ts";
|
|
4
|
+
|
|
5
|
+
export const AGENT_DEFINITION_PROJECTION_VERSION = 1 as const;
|
|
6
|
+
export const LAUNCH_BINDING_PROJECTION_VERSION = 1 as const;
|
|
7
|
+
|
|
8
|
+
function stableJson(value: unknown): string {
|
|
9
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
10
|
+
if (value && typeof value === "object") {
|
|
11
|
+
return `{${Object.entries(value as Record<string, unknown>)
|
|
12
|
+
.filter(([, entry]) => entry !== undefined)
|
|
13
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
14
|
+
.map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`)
|
|
15
|
+
.join(",")}}`;
|
|
16
|
+
}
|
|
17
|
+
return JSON.stringify(value);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function sha256(value: unknown): string {
|
|
21
|
+
return createHash("sha256").update(stableJson(value)).digest("hex");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function fileDigest(filePath: string): string | undefined {
|
|
25
|
+
try {
|
|
26
|
+
return createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
|
|
27
|
+
} catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Public-safe, deterministic evidence for the parsed launch-affecting agent definition. */
|
|
33
|
+
export function projectAgentDefinition(agent: AgentConfig): Record<string, unknown> {
|
|
34
|
+
return {
|
|
35
|
+
version: AGENT_DEFINITION_PROJECTION_VERSION,
|
|
36
|
+
name: agent.name,
|
|
37
|
+
localName: agent.localName,
|
|
38
|
+
packageName: agent.packageName,
|
|
39
|
+
filePath: agent.filePath,
|
|
40
|
+
fileContentDigest: fileDigest(agent.filePath),
|
|
41
|
+
systemPrompt: agent.systemPrompt,
|
|
42
|
+
systemPromptMode: agent.systemPromptMode,
|
|
43
|
+
inheritProjectContext: agent.inheritProjectContext,
|
|
44
|
+
inheritSkills: agent.inheritSkills,
|
|
45
|
+
model: agent.model,
|
|
46
|
+
fallbackModels: agent.fallbackModels,
|
|
47
|
+
thinking: agent.thinking,
|
|
48
|
+
tools: agent.tools,
|
|
49
|
+
mcpDirectTools: agent.mcpDirectTools,
|
|
50
|
+
extensions: agent.extensions,
|
|
51
|
+
subagentOnlyExtensions: agent.subagentOnlyExtensions,
|
|
52
|
+
skills: agent.skills,
|
|
53
|
+
skillPath: agent.skillPath,
|
|
54
|
+
output: agent.output,
|
|
55
|
+
defaultReads: agent.defaultReads,
|
|
56
|
+
defaultProgress: agent.defaultProgress,
|
|
57
|
+
defaultContext: agent.defaultContext,
|
|
58
|
+
defaultAsync: agent.defaultAsync,
|
|
59
|
+
defaultTimeoutMs: agent.defaultTimeoutMs,
|
|
60
|
+
defaultTurnBudget: agent.defaultTurnBudget,
|
|
61
|
+
defaultAcceptance: agent.defaultAcceptance,
|
|
62
|
+
acceptanceRole: agent.acceptanceRole,
|
|
63
|
+
interactive: agent.interactive,
|
|
64
|
+
maxSubagentDepth: agent.maxSubagentDepth,
|
|
65
|
+
completionGuard: agent.completionGuard,
|
|
66
|
+
toolBudget: agent.toolBudget,
|
|
67
|
+
memory: agent.memory,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function agentDefinitionDigest(agent: AgentConfig): string {
|
|
72
|
+
return sha256(projectAgentDefinition(agent));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface LaunchBindingInput {
|
|
76
|
+
definitionDigest: string;
|
|
77
|
+
/** Caller task; runtime acceptance/output task annotations are explicitly outside the preflight-known subset. */
|
|
78
|
+
task?: string;
|
|
79
|
+
model?: string;
|
|
80
|
+
modelCandidates?: string[];
|
|
81
|
+
thinking?: string;
|
|
82
|
+
systemPrompt?: string | null;
|
|
83
|
+
systemPromptMode?: AgentConfig["systemPromptMode"];
|
|
84
|
+
inheritProjectContext: boolean;
|
|
85
|
+
inheritSkills: boolean;
|
|
86
|
+
skills?: string[];
|
|
87
|
+
tools?: string[];
|
|
88
|
+
extensions?: string[];
|
|
89
|
+
subagentOnlyExtensions?: string[];
|
|
90
|
+
mcpDirectTools?: string[];
|
|
91
|
+
outputPath?: string;
|
|
92
|
+
outputMode?: string;
|
|
93
|
+
structuredOutputSchema?: unknown;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Canonical projection of the resolved inputs handed to the child. */
|
|
97
|
+
export function projectLaunchBinding(input: LaunchBindingInput): Record<string, unknown> {
|
|
98
|
+
return {
|
|
99
|
+
version: LAUNCH_BINDING_PROJECTION_VERSION,
|
|
100
|
+
definitionDigest: input.definitionDigest,
|
|
101
|
+
taskDigest: input.task === undefined ? undefined : sha256(input.task),
|
|
102
|
+
// The ordered candidate set already contains each attempted model; keeping only
|
|
103
|
+
// this set makes retries correlate to the same preflight binding.
|
|
104
|
+
modelCandidates: input.modelCandidates,
|
|
105
|
+
thinking: input.thinking,
|
|
106
|
+
systemPromptDigest: input.systemPrompt === undefined || input.systemPrompt === null ? undefined : sha256(input.systemPrompt),
|
|
107
|
+
systemPromptMode: input.systemPromptMode,
|
|
108
|
+
inheritProjectContext: input.inheritProjectContext,
|
|
109
|
+
inheritSkills: input.inheritSkills,
|
|
110
|
+
skills: input.skills,
|
|
111
|
+
tools: input.tools,
|
|
112
|
+
extensions: input.extensions,
|
|
113
|
+
subagentOnlyExtensions: input.subagentOnlyExtensions,
|
|
114
|
+
mcpDirectTools: input.mcpDirectTools,
|
|
115
|
+
outputPath: input.outputPath,
|
|
116
|
+
outputMode: input.outputMode,
|
|
117
|
+
structuredOutputSchema: input.structuredOutputSchema,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function launchBindingDigest(input: LaunchBindingInput): string {
|
|
122
|
+
return sha256(projectLaunchBinding(input));
|
|
123
|
+
}
|
package/src/shared/types.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type { AgentConfig } from "../agents/agents.ts";
|
|
|
9
9
|
import type { FSWatcher } from "node:fs";
|
|
10
10
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
11
11
|
import type { ModelScopeConfig } from "../runs/shared/model-scope.ts";
|
|
12
|
+
import type { ResolvedSubagentCapabilityCeiling, SubagentCapabilityAudit } from "../runs/shared/capability-ceiling.ts";
|
|
12
13
|
|
|
13
14
|
// ============================================================================
|
|
14
15
|
// Basic Types
|
|
@@ -302,7 +303,7 @@ export interface ExecutionProjection {
|
|
|
302
303
|
}
|
|
303
304
|
|
|
304
305
|
export interface ReviewProjection {
|
|
305
|
-
status: "not-requested" | "
|
|
306
|
+
status: "not-requested" | "review-required" | "reviewed" | "blockers";
|
|
306
307
|
findings?: AcceptanceReviewResult["findings"];
|
|
307
308
|
}
|
|
308
309
|
|
|
@@ -317,9 +318,69 @@ export interface EffectsProjection {
|
|
|
317
318
|
fileMutation?: FileMutationEffect;
|
|
318
319
|
}
|
|
319
320
|
|
|
320
|
-
export const SUBAGENT_LIFECYCLE_ARTIFACT_VERSION =
|
|
321
|
+
export const SUBAGENT_LIFECYCLE_ARTIFACT_VERSION = 3;
|
|
321
322
|
export type SubagentLifecycleArtifactVersion = typeof SUBAGENT_LIFECYCLE_ARTIFACT_VERSION;
|
|
322
323
|
|
|
324
|
+
export type ProcessTerminalState = "pending" | "observed" | "unknown" | "not-started";
|
|
325
|
+
export type ProcessTerminalReason =
|
|
326
|
+
| "observer-unavailable"
|
|
327
|
+
| "runner-candidate-missing"
|
|
328
|
+
| "runner-instance-mismatch"
|
|
329
|
+
| "writer-close-unverified"
|
|
330
|
+
| "canonical-session-unavailable"
|
|
331
|
+
| "canonical-session-lease-active"
|
|
332
|
+
| "canonical-session-release-unverified"
|
|
333
|
+
| "proof-write-failed"
|
|
334
|
+
| "stale-repair";
|
|
335
|
+
|
|
336
|
+
export interface RunnerProcessInstanceExitV1 {
|
|
337
|
+
processInstanceId: string;
|
|
338
|
+
kind: "runner";
|
|
339
|
+
closeObservedAt: number;
|
|
340
|
+
exitCode: number | null;
|
|
341
|
+
signal: string | null;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export interface PiWriterProcessInstanceExitV1 {
|
|
345
|
+
processInstanceId: string;
|
|
346
|
+
kind: "pi-writer";
|
|
347
|
+
attempt: number;
|
|
348
|
+
closeObservedAt: number;
|
|
349
|
+
exitCode: number | null;
|
|
350
|
+
signal: string | null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export type ProcessInstanceExitV1 = RunnerProcessInstanceExitV1 | PiWriterProcessInstanceExitV1;
|
|
354
|
+
|
|
355
|
+
export interface CanonicalSessionTerminalV1 {
|
|
356
|
+
canonicalSessionId: string;
|
|
357
|
+
leaseDisposition: "released" | "not-held";
|
|
358
|
+
freeAtObservation: true;
|
|
359
|
+
canonicalSessionLeaseReleased?: true;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
interface ProcessTerminalBaseV1 {
|
|
363
|
+
version: 1;
|
|
364
|
+
runId: string;
|
|
365
|
+
childIndex?: number;
|
|
366
|
+
runnerProcessInstanceId: string;
|
|
367
|
+
resumeDisposition?: "resumable" | "non-resumable" | "unavailable";
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export type ProcessTerminalV1 =
|
|
371
|
+
| (ProcessTerminalBaseV1 & { state: "pending" | "not-started" })
|
|
372
|
+
| (ProcessTerminalBaseV1 & {
|
|
373
|
+
state: "observed";
|
|
374
|
+
observedAt: number;
|
|
375
|
+
instances: ProcessInstanceExitV1[];
|
|
376
|
+
canonicalSession?: CanonicalSessionTerminalV1;
|
|
377
|
+
})
|
|
378
|
+
| (ProcessTerminalBaseV1 & {
|
|
379
|
+
state: "unknown";
|
|
380
|
+
reason: ProcessTerminalReason;
|
|
381
|
+
diagnostic?: string;
|
|
382
|
+
});
|
|
383
|
+
|
|
323
384
|
export type SteeringActionState = "delivered" | "scheduled" | "pending" | "partial" | "recovered" | "failed";
|
|
324
385
|
export type SteeringTargetState = "scheduled" | "routed" | "delivered" | "late" | "failed" | "recovered";
|
|
325
386
|
|
|
@@ -384,6 +445,7 @@ export interface SteeringNotice {
|
|
|
384
445
|
|
|
385
446
|
export interface SteeringRecoveryDescriptor {
|
|
386
447
|
version: 1;
|
|
448
|
+
launchContractDigest?: string;
|
|
387
449
|
sourceRunId: string;
|
|
388
450
|
agentContract?: AgentContract;
|
|
389
451
|
agent: string;
|
|
@@ -415,6 +477,7 @@ export interface SteeringRecoveryDescriptor {
|
|
|
415
477
|
initialToolBudget?: ResolvedToolBudget;
|
|
416
478
|
maxSubagentDepth: number;
|
|
417
479
|
maxOutput?: MaxOutputConfig;
|
|
480
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
418
481
|
share: boolean;
|
|
419
482
|
sessionDir?: string;
|
|
420
483
|
artifactsDir?: string;
|
|
@@ -532,7 +595,7 @@ export interface ModelAttempt {
|
|
|
532
595
|
usage?: Usage;
|
|
533
596
|
}
|
|
534
597
|
|
|
535
|
-
export type AcceptanceLevel = "auto" | "none" | "attested" | "checked" | "verified"
|
|
598
|
+
export type AcceptanceLevel = "auto" | "none" | "attested" | "checked" | "verified";
|
|
536
599
|
|
|
537
600
|
export type AcceptanceEvidenceKind =
|
|
538
601
|
| "changed-files"
|
|
@@ -641,7 +704,7 @@ export interface AcceptanceVerifyResult {
|
|
|
641
704
|
}
|
|
642
705
|
|
|
643
706
|
export interface AcceptanceReviewResult {
|
|
644
|
-
status: "
|
|
707
|
+
status: "review-required" | "reviewed" | "blockers";
|
|
645
708
|
findings: Array<{
|
|
646
709
|
severity: "blocker" | "non-blocking";
|
|
647
710
|
file?: string;
|
|
@@ -650,19 +713,24 @@ export interface AcceptanceReviewResult {
|
|
|
650
713
|
}>;
|
|
651
714
|
}
|
|
652
715
|
|
|
653
|
-
export type
|
|
716
|
+
export type AcceptanceEvidenceStatus =
|
|
654
717
|
| "pending"
|
|
655
718
|
| "not-required"
|
|
656
719
|
| "claimed"
|
|
657
720
|
| "attested"
|
|
658
721
|
| "checked"
|
|
659
722
|
| "verified"
|
|
660
|
-
| "reviewed"
|
|
661
|
-
| "accepted"
|
|
662
723
|
| "rejected";
|
|
663
724
|
|
|
725
|
+
export type AcceptanceLedgerStatus =
|
|
726
|
+
| AcceptanceEvidenceStatus
|
|
727
|
+
| "review-required"
|
|
728
|
+
| "reviewed"
|
|
729
|
+
| "accepted";
|
|
730
|
+
|
|
664
731
|
export interface AcceptanceLedger {
|
|
665
732
|
status: AcceptanceLedgerStatus;
|
|
733
|
+
evidenceStatus: AcceptanceEvidenceStatus;
|
|
666
734
|
explicit: boolean;
|
|
667
735
|
effectiveAcceptance: ResolvedAcceptanceConfig;
|
|
668
736
|
inferredReason: string[];
|
|
@@ -733,6 +801,7 @@ export interface SingleResult {
|
|
|
733
801
|
structuredOutputSchemaPath?: string;
|
|
734
802
|
acceptance?: AcceptanceLedger;
|
|
735
803
|
agentContract?: AgentContract;
|
|
804
|
+
launchContractDigest?: string;
|
|
736
805
|
execution?: ExecutionProjection;
|
|
737
806
|
review?: ReviewProjection;
|
|
738
807
|
effects?: EffectsProjection;
|
|
@@ -740,6 +809,8 @@ export interface SingleResult {
|
|
|
740
809
|
transcriptError?: string;
|
|
741
810
|
children?: NestedRunSummary[];
|
|
742
811
|
watchdog?: ChildWatchdogProgress;
|
|
812
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
813
|
+
capabilityAudit?: SubagentCapabilityAudit;
|
|
743
814
|
}
|
|
744
815
|
|
|
745
816
|
export interface SpawnBudgetGrant {
|
|
@@ -799,7 +870,13 @@ export interface Details {
|
|
|
799
870
|
// Aggregated cost across all agents in the run
|
|
800
871
|
totalCost?: CostSummary;
|
|
801
872
|
spawnBudget?: SpawnBudgetSnapshot;
|
|
873
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
874
|
+
capabilityAudit?: SubagentCapabilityAudit;
|
|
802
875
|
parallelHandoff?: ParallelHandoffReference;
|
|
876
|
+
lifecycleStatus?: {
|
|
877
|
+
processTerminal?: ProcessTerminalV1;
|
|
878
|
+
};
|
|
879
|
+
launchContractDigest?: string;
|
|
803
880
|
}
|
|
804
881
|
|
|
805
882
|
// ============================================================================
|
|
@@ -873,6 +950,9 @@ export interface NestedStepSummary {
|
|
|
873
950
|
wrapUpRequested?: boolean;
|
|
874
951
|
toolBudget?: ToolBudgetState;
|
|
875
952
|
toolBudgetBlocked?: boolean;
|
|
953
|
+
processTerminal?: ProcessTerminalV1;
|
|
954
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
955
|
+
capabilityAudit?: SubagentCapabilityAudit;
|
|
876
956
|
children?: NestedRunSummary[];
|
|
877
957
|
}
|
|
878
958
|
|
|
@@ -888,6 +968,9 @@ export interface NestedRunSummary extends NestedRunAddress {
|
|
|
888
968
|
controlInbox?: string;
|
|
889
969
|
capabilityToken?: string;
|
|
890
970
|
mode?: SubagentRunMode;
|
|
971
|
+
processTerminal?: ProcessTerminalV1;
|
|
972
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
973
|
+
capabilityAudit?: SubagentCapabilityAudit;
|
|
891
974
|
state: NestedRunState;
|
|
892
975
|
agent?: string;
|
|
893
976
|
agents?: string[];
|
|
@@ -946,10 +1029,12 @@ export interface AsyncStartedEvent {
|
|
|
946
1029
|
chainStepCount?: number;
|
|
947
1030
|
parallelGroups?: AsyncParallelGroupStatus[];
|
|
948
1031
|
workflowGraph?: WorkflowGraphSnapshot;
|
|
1032
|
+
launchContractDigest?: string;
|
|
949
1033
|
timeoutMs?: number;
|
|
950
1034
|
deadlineAt?: number;
|
|
951
1035
|
turnBudget?: TurnBudgetState;
|
|
952
1036
|
nestedRoute?: NestedRouteInfo;
|
|
1037
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
953
1038
|
}
|
|
954
1039
|
|
|
955
1040
|
export interface AsyncStatus {
|
|
@@ -987,6 +1072,10 @@ export interface AsyncStatus {
|
|
|
987
1072
|
pendingAppends?: number;
|
|
988
1073
|
parallelGroups?: AsyncParallelGroupStatus[];
|
|
989
1074
|
workflowGraph?: WorkflowGraphSnapshot;
|
|
1075
|
+
processTerminal?: ProcessTerminalV1;
|
|
1076
|
+
launchContractDigest?: string;
|
|
1077
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
1078
|
+
capabilityAudit?: SubagentCapabilityAudit;
|
|
990
1079
|
steps?: Array<{
|
|
991
1080
|
agent: string;
|
|
992
1081
|
/** Resolved launch context for this child step. */
|
|
@@ -1035,10 +1124,14 @@ export interface AsyncStatus {
|
|
|
1035
1124
|
structuredOutputSchemaPath?: string;
|
|
1036
1125
|
acceptance?: AcceptanceLedger;
|
|
1037
1126
|
agentContract?: AgentContract;
|
|
1127
|
+
launchContractDigest?: string;
|
|
1038
1128
|
execution?: ExecutionProjection;
|
|
1039
1129
|
review?: ReviewProjection;
|
|
1040
1130
|
effects?: EffectsProjection;
|
|
1041
1131
|
watchdog?: ChildWatchdogProgress;
|
|
1132
|
+
processTerminal?: ProcessTerminalV1;
|
|
1133
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
1134
|
+
capabilityAudit?: SubagentCapabilityAudit;
|
|
1042
1135
|
}>;
|
|
1043
1136
|
sessionDir?: string;
|
|
1044
1137
|
outputFile?: string;
|
|
@@ -1133,6 +1226,8 @@ export interface ForegroundResumeChild {
|
|
|
1133
1226
|
execution?: ExecutionProjection;
|
|
1134
1227
|
review?: ReviewProjection;
|
|
1135
1228
|
effects?: EffectsProjection;
|
|
1229
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
1230
|
+
capabilityAudit?: SubagentCapabilityAudit;
|
|
1136
1231
|
updatedAt?: number;
|
|
1137
1232
|
}
|
|
1138
1233
|
|
|
@@ -1252,6 +1347,7 @@ export const INTERCOM_DETACH_REQUEST_EVENT = "pi-intercom:detach-request";
|
|
|
1252
1347
|
export const INTERCOM_DETACH_RESPONSE_EVENT = "pi-intercom:detach-response";
|
|
1253
1348
|
export const SUBAGENT_ASYNC_STARTED_EVENT = "subagent:async-started";
|
|
1254
1349
|
export const SUBAGENT_ASYNC_COMPLETE_EVENT = "subagent:async-complete";
|
|
1350
|
+
export const SUBAGENT_PROCESS_TERMINAL_EVENT = "subagent:process-terminal";
|
|
1255
1351
|
export const SUBAGENT_FOREGROUND_COMPLETE_EVENT = "subagent:foreground-complete";
|
|
1256
1352
|
export const SUBAGENT_CONTROL_EVENT = "subagent:control-event";
|
|
1257
1353
|
export const SUBAGENT_CONTROL_INTERCOM_EVENT = "subagent:control-intercom";
|
|
@@ -1299,6 +1395,7 @@ export interface RunSyncOptions {
|
|
|
1299
1395
|
maxSubagentDepth?: number;
|
|
1300
1396
|
/** Effective parent wait-tool setting propagated to the child runtime. */
|
|
1301
1397
|
waitToolEnabled?: boolean;
|
|
1398
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
1302
1399
|
nestedRoute?: NestedRouteInfo;
|
|
1303
1400
|
/** Override the agent's default model (format: "provider/id" or just "id") */
|
|
1304
1401
|
modelOverride?: string;
|
package/src/shared/utils.ts
CHANGED
|
@@ -412,6 +412,14 @@ export function compactForegroundDetails(details: Details): Details {
|
|
|
412
412
|
};
|
|
413
413
|
}
|
|
414
414
|
|
|
415
|
+
export function hasEmptyTerminalAssistantResponse(messages: Message[]): boolean {
|
|
416
|
+
const lastAssistant = messages.findLast((message) => message.role === "assistant");
|
|
417
|
+
return lastAssistant?.role === "assistant"
|
|
418
|
+
&& Array.isArray(lastAssistant.content)
|
|
419
|
+
&& lastAssistant.content.length === 0
|
|
420
|
+
&& lastAssistant.usage.output === 0;
|
|
421
|
+
}
|
|
422
|
+
|
|
415
423
|
/**
|
|
416
424
|
* Detect errors in subagent execution from messages (only errors with no subsequent success)
|
|
417
425
|
*/
|
|
@@ -438,50 +446,17 @@ export function detectSubagentError(messages: Message[]): ErrorInfo {
|
|
|
438
446
|
const toolName = "toolName" in msg && typeof msg.toolName === "string" ? msg.toolName : undefined;
|
|
439
447
|
const isError = "isError" in msg && msg.isError === true;
|
|
440
448
|
|
|
441
|
-
if (isError)
|
|
442
|
-
const text = msg.content.find((c) => c.type === "text");
|
|
443
|
-
const details = text && "text" in text ? text.text : undefined;
|
|
444
|
-
const exitMatch = details?.match(/exit(?:ed)?\s*(?:with\s*)?(?:code|status)?\s*[:\s]?\s*(\d+)/i);
|
|
445
|
-
return {
|
|
446
|
-
hasError: true,
|
|
447
|
-
exitCode: exitMatch ? parseInt(exitMatch[1], 10) : 1,
|
|
448
|
-
errorType: toolName || "tool",
|
|
449
|
-
details: details?.slice(0, 200),
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
if (toolName !== "bash") continue;
|
|
449
|
+
if (!isError) continue;
|
|
454
450
|
|
|
455
451
|
const text = msg.content.find((c) => c.type === "text");
|
|
456
|
-
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// NOTE: These patterns can match legitimate output (grep results, logs,
|
|
468
|
-
// testing). With the assistant-message check above, most false positives
|
|
469
|
-
// are mitigated since the agent will have responded after routine errors.
|
|
470
|
-
const fatalPatterns = [
|
|
471
|
-
/command not found/i,
|
|
472
|
-
/permission denied/i,
|
|
473
|
-
/no such file or directory/i,
|
|
474
|
-
/segmentation fault/i,
|
|
475
|
-
/killed|terminated/i,
|
|
476
|
-
/out of memory/i,
|
|
477
|
-
/connection refused/i,
|
|
478
|
-
/timeout/i,
|
|
479
|
-
];
|
|
480
|
-
for (const pattern of fatalPatterns) {
|
|
481
|
-
if (pattern.test(output)) {
|
|
482
|
-
return { hasError: true, exitCode: 1, errorType: "bash", details: output.slice(0, 200) };
|
|
483
|
-
}
|
|
484
|
-
}
|
|
452
|
+
const details = text && "text" in text ? text.text : undefined;
|
|
453
|
+
const exitMatch = details?.match(/exit(?:ed)?\s*(?:with\s*)?(?:code|status)?\s*[:\s]?\s*(\d+)/i);
|
|
454
|
+
return {
|
|
455
|
+
hasError: true,
|
|
456
|
+
exitCode: exitMatch ? parseInt(exitMatch[1], 10) : 1,
|
|
457
|
+
errorType: toolName || "tool",
|
|
458
|
+
details: details?.slice(0, 200),
|
|
459
|
+
};
|
|
485
460
|
}
|
|
486
461
|
|
|
487
462
|
return { hasError: false };
|
|
@@ -491,7 +491,7 @@ export function toSubagentDelegationResponse(
|
|
|
491
491
|
...(child?.finalOutput ? { output: child.finalOutput } : {}),
|
|
492
492
|
...(child?.savedOutputPath ? { outputPath: child.savedOutputPath } : {}),
|
|
493
493
|
...(child?.sessionFile ? { sessionFile: child.sessionFile } : {}),
|
|
494
|
-
...(child?.acceptance ? { acceptance: { status: child.acceptance.status, explicit: child.acceptance.explicit } } : {}),
|
|
494
|
+
...(child?.acceptance ? { acceptance: { status: child.acceptance.status, evidenceStatus: child.acceptance.evidenceStatus, explicit: child.acceptance.explicit } } : {}),
|
|
495
495
|
...(child?.review ? { review: child.review as SubagentDelegationReviewResult } : {}),
|
|
496
496
|
...(child?.effects ? { effects: child.effects as SubagentDelegationEffectsResult } : {}),
|
|
497
497
|
...(typeof child?.usage?.turns === "number" ? { turns: child.usage.turns } : {}),
|
|
@@ -1008,7 +1008,7 @@ function validateInlineAcceptanceInput(value: string, agent: string): void {
|
|
|
1008
1008
|
const errors = validateAcceptanceInput(value, `acceptance for step '${agent}'`);
|
|
1009
1009
|
if (errors.length > 0) throw new SlashParseError(errors[0]!);
|
|
1010
1010
|
if (!INLINE_ACCEPTANCE_LEVELS.has(value)) {
|
|
1011
|
-
throw new SlashParseError(`Inline acceptance for step '${agent}' supports auto, attested, or checked. Use the subagent tool API or a saved .chain.json file for none or
|
|
1011
|
+
throw new SlashParseError(`Inline acceptance for step '${agent}' supports auto, attested, or checked. Use the subagent tool API or a saved .chain.json file for none, verified, or review requirements; reviewed is an achieved status, not an input level.`);
|
|
1012
1012
|
}
|
|
1013
1013
|
}
|
|
1014
1014
|
|