pi-subagents 0.36.0 → 0.37.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/CHANGELOG.md +38 -0
- package/README.md +111 -16
- package/agents/delegate.md +2 -0
- package/agents/planner.md +2 -1
- package/agents/worker.md +2 -0
- package/package.json +6 -4
- package/skills/pi-subagents/SKILL.md +18 -973
- package/skills/pi-subagents/references/constraints-and-recipes.md +256 -0
- package/skills/pi-subagents/references/execution-controls.md +411 -0
- package/skills/pi-subagents/references/management-authoring-rpc.md +140 -0
- package/skills/pi-subagents/references/prompting-and-roles.md +268 -0
- 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 +6 -1
- package/src/api/preflight.ts +399 -0
- package/src/extension/index.ts +16 -7
- package/src/extension/rpc.ts +29 -2
- package/src/extension/schemas.ts +10 -4
- package/src/extension/tool-description.ts +4 -2
- package/src/intercom/intercom-bridge.ts +5 -2
- package/src/runs/background/async-execution.ts +125 -14
- package/src/runs/background/async-job-tracker.ts +19 -12
- package/src/runs/background/async-resume.ts +27 -9
- package/src/runs/background/async-status.ts +18 -0
- package/src/runs/background/notify.ts +3 -0
- package/src/runs/background/process-terminal.ts +280 -0
- package/src/runs/background/result-watcher.ts +9 -3
- 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 +142 -47
- 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 +56 -17
- package/src/runs/shared/model-fallback.ts +8 -0
- 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/task-intent.ts +1 -1
- package/src/runs/shared/tool-availability.ts +18 -2
- package/src/shared/launch-contract.ts +123 -0
- package/src/shared/types.ts +118 -8
- package/src/shared/utils.ts +17 -42
- package/src/slash/delegation-adapters.ts +6 -1
- package/src/slash/slash-commands.ts +1 -1
- package/src/tui/fleet-status.ts +62 -16
- package/src/tui/fleet.ts +1 -1
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { discoverAgents, discoverAgentsAll, type AgentConfig, type AgentScope, type AgentSource } from "../agents/agents.ts";
|
|
6
|
+
import { resolveExecutionAgentScope } from "../agents/agent-scope.ts";
|
|
7
|
+
import { buildSkillInjection, normalizeSkillInput, resolveSkillsWithFallback } from "../agents/skills.ts";
|
|
8
|
+
import { buildAgentMemoryInjection } from "../agents/agent-memory.ts";
|
|
9
|
+
import { buildModelCandidates, resolveEffectiveSubagentModel, type AvailableModelInfo, type ParentModel } from "../runs/shared/model-fallback.ts";
|
|
10
|
+
import { applyThinkingSuffix, resolvePiLaunchToolPlan, type PiLaunchToolPlan } from "../runs/shared/pi-args.ts";
|
|
11
|
+
import { injectOutputPathSystemPrompt, normalizeSingleOutputOverride, resolveSingleOutputPath } from "../runs/shared/single-output.ts";
|
|
12
|
+
import { getArtifactPaths, getArtifactsDir } from "../shared/artifacts.ts";
|
|
13
|
+
import { resolveEffectiveThinking } from "../shared/model-info.ts";
|
|
14
|
+
import { SUBAGENT_LIFECYCLE_ARTIFACT_VERSION, type ArtifactDirPreference, type ArtifactPaths, type JsonSchemaObject, type OutputMode } from "../shared/types.ts";
|
|
15
|
+
import type { ResolvedSubagentCapabilityCeiling, SubagentCapabilityAudit } from "../runs/shared/capability-ceiling.ts";
|
|
16
|
+
import { appendTurnBudgetSystemPrompt } from "../runs/shared/turn-budget.ts";
|
|
17
|
+
import type { ResolvedTurnBudget } from "../shared/types.ts";
|
|
18
|
+
import type { ResolvedMcpDirectToolSelection } from "../runs/shared/mcp-direct-tool-allowlist.ts";
|
|
19
|
+
import { resolveStepBehavior } from "../shared/settings.ts";
|
|
20
|
+
import { agentDefinitionDigest, AGENT_DEFINITION_PROJECTION_VERSION, launchBindingDigest } from "../shared/launch-contract.ts";
|
|
21
|
+
import { ASYNC_DIR, RESULTS_DIR, TEMP_ROOT_DIR } from "../shared/types.ts";
|
|
22
|
+
import { processTerminalCandidatePath, processTerminalPath } from "../runs/background/process-terminal.ts";
|
|
23
|
+
import { nestedResultsPath } from "../runs/shared/nested-events.ts";
|
|
24
|
+
|
|
25
|
+
export const SUBAGENT_LAUNCH_CONTRACT_VERSION = 2 as const;
|
|
26
|
+
|
|
27
|
+
export type SubagentLaunchContractReasonCode =
|
|
28
|
+
| "missing_agent"
|
|
29
|
+
| "ambiguous_agent"
|
|
30
|
+
| "missing_skill"
|
|
31
|
+
| "denied_required_tool"
|
|
32
|
+
| "invalid_artifact_dir"
|
|
33
|
+
| "invalid_cwd"
|
|
34
|
+
| "unsupported_mode";
|
|
35
|
+
|
|
36
|
+
export interface SubagentLaunchContractDiagnostic {
|
|
37
|
+
code: SubagentLaunchContractReasonCode | "host_required" | "snapshot_warning";
|
|
38
|
+
severity: "error" | "warning" | "host-required";
|
|
39
|
+
message: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface SubagentLaunchContractInput {
|
|
43
|
+
agent: string;
|
|
44
|
+
cwd: string;
|
|
45
|
+
task?: string;
|
|
46
|
+
agentScope?: AgentScope;
|
|
47
|
+
context?: "fresh" | "fork";
|
|
48
|
+
model?: string;
|
|
49
|
+
thinking?: string | false;
|
|
50
|
+
parentModel?: ParentModel;
|
|
51
|
+
availableModels?: ReadonlyArray<AvailableModelInfo | { provider: string; id: string; fullId?: string; reasoning?: boolean }>;
|
|
52
|
+
preferredProvider?: string;
|
|
53
|
+
skill?: string | string[] | boolean;
|
|
54
|
+
output?: string | boolean;
|
|
55
|
+
outputMode?: OutputMode;
|
|
56
|
+
outputSchema?: JsonSchemaObject;
|
|
57
|
+
turnBudget?: ResolvedTurnBudget;
|
|
58
|
+
artifacts?: boolean;
|
|
59
|
+
artifactDir?: ArtifactDirPreference;
|
|
60
|
+
parentSessionFile?: string | null;
|
|
61
|
+
sessionRoot?: string;
|
|
62
|
+
sessionDir?: string;
|
|
63
|
+
runId?: string;
|
|
64
|
+
/** Root run id supplied by a host when projecting nested async lifecycle paths. */
|
|
65
|
+
nestedRootRunId?: string;
|
|
66
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
67
|
+
inheritedCapabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface SubagentLaunchContractAgentCandidate {
|
|
71
|
+
name: string;
|
|
72
|
+
localName?: string;
|
|
73
|
+
packageName?: string;
|
|
74
|
+
source: AgentSource;
|
|
75
|
+
filePath: string;
|
|
76
|
+
disabled?: boolean;
|
|
77
|
+
selected: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface SubagentLaunchContractAgent {
|
|
81
|
+
name: string;
|
|
82
|
+
localName?: string;
|
|
83
|
+
packageName?: string;
|
|
84
|
+
source: AgentSource;
|
|
85
|
+
filePath: string;
|
|
86
|
+
definitionProjectionVersion: typeof AGENT_DEFINITION_PROJECTION_VERSION;
|
|
87
|
+
definitionDigest: string;
|
|
88
|
+
shadowedCandidates: SubagentLaunchContractAgentCandidate[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface SubagentLaunchContractSkills {
|
|
92
|
+
requested: string[];
|
|
93
|
+
resolved: Array<{ name: string; path: string; source: string }>;
|
|
94
|
+
missing: string[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface SubagentLaunchContractTools {
|
|
98
|
+
requestedBuiltin: string[];
|
|
99
|
+
declaredBuiltin: string[];
|
|
100
|
+
effectiveAllowlist: string[];
|
|
101
|
+
explicitAllowlist: boolean;
|
|
102
|
+
requiredChildTools: string[];
|
|
103
|
+
internalTools: string[];
|
|
104
|
+
mcp: ResolvedMcpDirectToolSelection[];
|
|
105
|
+
effectiveMcpTools: string[];
|
|
106
|
+
toolExtensionPaths: string[];
|
|
107
|
+
runtimeExtensions: string[];
|
|
108
|
+
configuredExtensions: string[];
|
|
109
|
+
extensionArgs: string[];
|
|
110
|
+
disableAmbientExtensions: boolean;
|
|
111
|
+
fanoutAuthorized: boolean;
|
|
112
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
113
|
+
capabilityAudit?: SubagentCapabilityAudit;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface SubagentLaunchContractRoots {
|
|
117
|
+
cwd: string;
|
|
118
|
+
sessionRoot?: string;
|
|
119
|
+
sessionDir?: string;
|
|
120
|
+
sessionFile?: string;
|
|
121
|
+
artifactsDir?: string;
|
|
122
|
+
artifactPaths?: ArtifactPaths;
|
|
123
|
+
outputPath?: string;
|
|
124
|
+
lifecycle?: {
|
|
125
|
+
asyncDir: string;
|
|
126
|
+
resultPath: string;
|
|
127
|
+
statusPath: string;
|
|
128
|
+
eventsPath: string;
|
|
129
|
+
processTerminalPath: string;
|
|
130
|
+
processTerminalCandidatePath: string;
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface SubagentLaunchContract {
|
|
135
|
+
version: typeof SUBAGENT_LAUNCH_CONTRACT_VERSION;
|
|
136
|
+
runId: string;
|
|
137
|
+
agent: SubagentLaunchContractAgent;
|
|
138
|
+
context: "fresh" | "fork";
|
|
139
|
+
model?: string;
|
|
140
|
+
modelCandidates: string[];
|
|
141
|
+
thinking?: string;
|
|
142
|
+
systemPromptMode: AgentConfig["systemPromptMode"];
|
|
143
|
+
inheritProjectContext: boolean;
|
|
144
|
+
inheritSkills: boolean;
|
|
145
|
+
skills: SubagentLaunchContractSkills;
|
|
146
|
+
tools: SubagentLaunchContractTools;
|
|
147
|
+
roots: SubagentLaunchContractRoots;
|
|
148
|
+
protocol: {
|
|
149
|
+
lifecycleArtifactVersion: number;
|
|
150
|
+
packageVersion: string;
|
|
151
|
+
};
|
|
152
|
+
diagnostics: SubagentLaunchContractDiagnostic[];
|
|
153
|
+
/** Digest of the resolved child inputs, recomputed by execution paths. */
|
|
154
|
+
launchContractDigest: string;
|
|
155
|
+
digest: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export type SubagentLaunchContractResult =
|
|
159
|
+
| { ok: true; contract: SubagentLaunchContract }
|
|
160
|
+
| { ok: false; code: SubagentLaunchContractReasonCode; message: string; diagnostics: SubagentLaunchContractDiagnostic[] };
|
|
161
|
+
|
|
162
|
+
function packageVersion(): string {
|
|
163
|
+
const packagePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "package.json");
|
|
164
|
+
const parsed = JSON.parse(fs.readFileSync(packagePath, "utf-8")) as { version?: unknown };
|
|
165
|
+
if (typeof parsed.version !== "string" || !parsed.version.trim()) {
|
|
166
|
+
throw new Error(`Invalid package version in '${packagePath}'.`);
|
|
167
|
+
}
|
|
168
|
+
return parsed.version;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function stableJson(value: unknown): string {
|
|
172
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
173
|
+
if (value && typeof value === "object") {
|
|
174
|
+
return `{${Object.entries(value as Record<string, unknown>)
|
|
175
|
+
.filter(([, entry]) => entry !== undefined)
|
|
176
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
177
|
+
.map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`)
|
|
178
|
+
.join(",")}}`;
|
|
179
|
+
}
|
|
180
|
+
return JSON.stringify(value);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function digestContract(contract: Omit<SubagentLaunchContract, "digest">): string {
|
|
184
|
+
return createHash("sha256").update(stableJson(contract)).digest("hex");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function normalizeAvailableModels(models: SubagentLaunchContractInput["availableModels"]): AvailableModelInfo[] {
|
|
188
|
+
return (models ?? []).map((model) => ({ ...model, fullId: model.fullId ?? `${model.provider}/${model.id}` }));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function candidateList(inputAgent: string, selected: AgentConfig | undefined, cwd: string): SubagentLaunchContractAgentCandidate[] {
|
|
192
|
+
const all = discoverAgentsAll(cwd);
|
|
193
|
+
return [...all.builtin, ...all.package, ...all.user, ...all.project]
|
|
194
|
+
.filter((agent) => agent.name === inputAgent || agent.localName === inputAgent)
|
|
195
|
+
.map((agent) => ({
|
|
196
|
+
name: agent.name,
|
|
197
|
+
...(agent.localName ? { localName: agent.localName } : {}),
|
|
198
|
+
...(agent.packageName ? { packageName: agent.packageName } : {}),
|
|
199
|
+
source: agent.source,
|
|
200
|
+
filePath: agent.filePath,
|
|
201
|
+
...(agent.disabled === true ? { disabled: true } : {}),
|
|
202
|
+
selected: Boolean(selected && agent.filePath === selected.filePath && agent.name === selected.name),
|
|
203
|
+
}));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export async function resolveSubagentLaunchContract(input: SubagentLaunchContractInput): Promise<SubagentLaunchContractResult> {
|
|
207
|
+
const diagnostics: SubagentLaunchContractDiagnostic[] = [];
|
|
208
|
+
const effectiveCwd = path.resolve(input.cwd);
|
|
209
|
+
try {
|
|
210
|
+
if (!fs.statSync(effectiveCwd).isDirectory()) {
|
|
211
|
+
return { ok: false, code: "invalid_cwd", message: `cwd '${effectiveCwd}' is not a directory.`, diagnostics };
|
|
212
|
+
}
|
|
213
|
+
} catch (error) {
|
|
214
|
+
const detail = error instanceof Error ? ` ${error.message}` : "";
|
|
215
|
+
return { ok: false, code: "invalid_cwd", message: `cwd '${effectiveCwd}' is not a directory.${detail}`, diagnostics };
|
|
216
|
+
}
|
|
217
|
+
if (input.context !== undefined && input.context !== "fresh" && input.context !== "fork") {
|
|
218
|
+
return { ok: false, code: "unsupported_mode", message: `Unsupported context '${String(input.context)}'; expected 'fresh' or 'fork'.`, diagnostics };
|
|
219
|
+
}
|
|
220
|
+
if (input.artifactDir !== undefined && input.artifactDir !== "project" && input.artifactDir !== "session" && input.artifactDir !== "temp") {
|
|
221
|
+
return { ok: false, code: "invalid_artifact_dir", message: `Unsupported artifactDir '${String(input.artifactDir)}'; expected 'project', 'session', or 'temp'.`, diagnostics };
|
|
222
|
+
}
|
|
223
|
+
if (input.context === "fork") {
|
|
224
|
+
diagnostics.push({ code: "host_required", severity: "host-required", message: "Exact fork session branching and fork-thinking downgrade checks require Pi host session and model-registry snapshots." });
|
|
225
|
+
}
|
|
226
|
+
const scope = resolveExecutionAgentScope(input.agentScope);
|
|
227
|
+
const discovered = discoverAgents(effectiveCwd, scope);
|
|
228
|
+
const matches = discovered.agents.filter((agent) => agent.name === input.agent || agent.localName === input.agent);
|
|
229
|
+
if (matches.length === 0) {
|
|
230
|
+
return { ok: false, code: "missing_agent", message: `Unknown agent: ${input.agent}`, diagnostics };
|
|
231
|
+
}
|
|
232
|
+
if (matches.length > 1) {
|
|
233
|
+
return { ok: false, code: "ambiguous_agent", message: `Ambiguous agent: ${input.agent}`, diagnostics };
|
|
234
|
+
}
|
|
235
|
+
const agent = matches[0]!;
|
|
236
|
+
const runId = input.runId ?? "preflight";
|
|
237
|
+
const skillInput = normalizeSkillInput(input.skill);
|
|
238
|
+
const outputOverride = normalizeSingleOutputOverride(input.output, agent.output);
|
|
239
|
+
const behavior = resolveStepBehavior(agent, {
|
|
240
|
+
...(outputOverride !== undefined ? { output: outputOverride } : {}),
|
|
241
|
+
...(input.outputMode !== undefined ? { outputMode: input.outputMode } : {}),
|
|
242
|
+
...(skillInput !== undefined ? { skills: skillInput } : {}),
|
|
243
|
+
...(input.model !== undefined ? { model: input.model } : {}),
|
|
244
|
+
});
|
|
245
|
+
const requestedSkills = behavior.skills === false ? [] : behavior.skills;
|
|
246
|
+
const resolvedSkills = resolveSkillsWithFallback(
|
|
247
|
+
requestedSkills,
|
|
248
|
+
effectiveCwd,
|
|
249
|
+
effectiveCwd,
|
|
250
|
+
agent.skillPath,
|
|
251
|
+
agent.filePath ? path.dirname(agent.filePath) : effectiveCwd,
|
|
252
|
+
);
|
|
253
|
+
if (resolvedSkills.missing.includes("pi-subagents")) {
|
|
254
|
+
return { ok: false, code: "missing_skill", message: "The pi-subagents orchestration skill is not child-injectable.", diagnostics };
|
|
255
|
+
}
|
|
256
|
+
if (resolvedSkills.missing.length > 0) diagnostics.push({ code: "missing_skill", severity: "error", message: `Missing skills: ${resolvedSkills.missing.join(", ")}` });
|
|
257
|
+
|
|
258
|
+
const availableModels = normalizeAvailableModels(input.availableModels);
|
|
259
|
+
const preferredProvider = input.preferredProvider ?? input.parentModel?.provider;
|
|
260
|
+
const primaryModel = resolveEffectiveSubagentModel(input.model, agent.model, input.parentModel, availableModels, preferredProvider, { scope: discovered.modelScope });
|
|
261
|
+
const effectiveThinkingConfig = input.thinking !== undefined ? input.thinking : agent.thinking;
|
|
262
|
+
const model = applyThinkingSuffix(primaryModel, effectiveThinkingConfig, input.thinking !== undefined);
|
|
263
|
+
const modelCandidates = buildModelCandidates(primaryModel, agent.fallbackModels, availableModels, preferredProvider, { scope: discovered.modelScope })
|
|
264
|
+
.map((candidate) => applyThinkingSuffix(candidate, effectiveThinkingConfig, input.thinking !== undefined) ?? candidate);
|
|
265
|
+
let toolPlan: PiLaunchToolPlan;
|
|
266
|
+
try {
|
|
267
|
+
toolPlan = resolvePiLaunchToolPlan({
|
|
268
|
+
tools: agent.tools,
|
|
269
|
+
extensions: agent.extensions,
|
|
270
|
+
subagentOnlyExtensions: agent.subagentOnlyExtensions,
|
|
271
|
+
mcpDirectTools: agent.mcpDirectTools,
|
|
272
|
+
cwd: effectiveCwd,
|
|
273
|
+
requireReadTool: resolvedSkills.resolved.length > 0,
|
|
274
|
+
structuredOutput: Boolean(input.outputSchema),
|
|
275
|
+
capabilityCeiling: input.capabilityCeiling,
|
|
276
|
+
inheritedCapabilityCeiling: input.inheritedCapabilityCeiling,
|
|
277
|
+
});
|
|
278
|
+
} catch (error) {
|
|
279
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
280
|
+
diagnostics.push({ code: "denied_required_tool", severity: "error", message });
|
|
281
|
+
return { ok: false, code: "denied_required_tool", message, diagnostics };
|
|
282
|
+
}
|
|
283
|
+
const artifactsEnabled = input.artifacts !== false;
|
|
284
|
+
const artifactsDir = artifactsEnabled ? getArtifactsDir(input.parentSessionFile ?? null, effectiveCwd, input.artifactDir ?? "project") : undefined;
|
|
285
|
+
const artifactPaths = artifactsDir ? getArtifactPaths(artifactsDir, runId, agent.name, 0) : undefined;
|
|
286
|
+
const outputPath = resolveSingleOutputPath(behavior.output, effectiveCwd, effectiveCwd, artifactsDir ? path.join(artifactsDir, "outputs", runId) : undefined);
|
|
287
|
+
const sessionRoot = input.sessionDir ? path.resolve(input.sessionDir) : input.sessionRoot ? path.join(path.resolve(input.sessionRoot), runId) : undefined;
|
|
288
|
+
const sessionDir = sessionRoot ? path.join(sessionRoot, "run-0") : undefined;
|
|
289
|
+
const lifecycleAsyncDir = input.nestedRootRunId
|
|
290
|
+
? path.join(TEMP_ROOT_DIR, "nested-subagent-runs", input.nestedRootRunId, runId)
|
|
291
|
+
: path.join(ASYNC_DIR, runId);
|
|
292
|
+
const lifecycleResultPath = input.nestedRootRunId
|
|
293
|
+
? nestedResultsPath(input.nestedRootRunId, runId)
|
|
294
|
+
: path.join(RESULTS_DIR, `${runId}.json`);
|
|
295
|
+
if (!sessionDir) diagnostics.push({ code: "host_required", severity: "host-required", message: "No sessionRoot/sessionDir was supplied; exact child session paths require the Pi host session-root policy." });
|
|
296
|
+
if (input.availableModels === undefined && (input.model || agent.model || input.parentModel)) {
|
|
297
|
+
diagnostics.push({ code: "host_required", severity: "host-required", message: "No availableModels snapshot was supplied; model resolution may differ from the active Pi host registry." });
|
|
298
|
+
}
|
|
299
|
+
if (resolvedSkills.missing.length > 0) {
|
|
300
|
+
return { ok: false, code: "missing_skill", message: `Missing skills: ${resolvedSkills.missing.join(", ")}`, diagnostics };
|
|
301
|
+
}
|
|
302
|
+
let effectiveSystemPrompt = agent.systemPrompt?.trim() ?? "";
|
|
303
|
+
if (resolvedSkills.resolved.length > 0) {
|
|
304
|
+
const skillInjection = buildSkillInjection(resolvedSkills.resolved);
|
|
305
|
+
effectiveSystemPrompt = effectiveSystemPrompt ? `${effectiveSystemPrompt}\n\n${skillInjection}` : skillInjection;
|
|
306
|
+
}
|
|
307
|
+
const memoryInjection = buildAgentMemoryInjection(agent, effectiveCwd);
|
|
308
|
+
if (memoryInjection) effectiveSystemPrompt = effectiveSystemPrompt ? `${effectiveSystemPrompt}\n\n${memoryInjection}` : memoryInjection;
|
|
309
|
+
effectiveSystemPrompt = injectOutputPathSystemPrompt(effectiveSystemPrompt, outputPath, agent);
|
|
310
|
+
const turnBudget = input.turnBudget ?? agent.defaultTurnBudget;
|
|
311
|
+
effectiveSystemPrompt = appendTurnBudgetSystemPrompt(effectiveSystemPrompt, turnBudget);
|
|
312
|
+
const candidates = candidateList(input.agent, agent, effectiveCwd);
|
|
313
|
+
const shadowedCandidates = candidates.filter((candidate) => !candidate.selected);
|
|
314
|
+
const definitionDigest = agentDefinitionDigest(agent);
|
|
315
|
+
const contractBase: Omit<SubagentLaunchContract, "digest"> = {
|
|
316
|
+
version: SUBAGENT_LAUNCH_CONTRACT_VERSION,
|
|
317
|
+
runId,
|
|
318
|
+
agent: {
|
|
319
|
+
name: agent.name,
|
|
320
|
+
...(agent.localName ? { localName: agent.localName } : {}),
|
|
321
|
+
...(agent.packageName ? { packageName: agent.packageName } : {}),
|
|
322
|
+
source: agent.source,
|
|
323
|
+
filePath: agent.filePath,
|
|
324
|
+
definitionProjectionVersion: AGENT_DEFINITION_PROJECTION_VERSION,
|
|
325
|
+
definitionDigest,
|
|
326
|
+
shadowedCandidates,
|
|
327
|
+
},
|
|
328
|
+
context: input.context ?? agent.defaultContext ?? "fresh",
|
|
329
|
+
...(model ? { model } : {}),
|
|
330
|
+
modelCandidates,
|
|
331
|
+
...(resolveEffectiveThinking(model, effectiveThinkingConfig) ? { thinking: resolveEffectiveThinking(model, effectiveThinkingConfig) } : {}),
|
|
332
|
+
systemPromptMode: agent.systemPromptMode,
|
|
333
|
+
inheritProjectContext: agent.inheritProjectContext,
|
|
334
|
+
inheritSkills: agent.inheritSkills,
|
|
335
|
+
skills: {
|
|
336
|
+
requested: requestedSkills,
|
|
337
|
+
resolved: resolvedSkills.resolved.map((skill) => ({ name: skill.name, path: skill.path, source: skill.source })),
|
|
338
|
+
missing: resolvedSkills.missing,
|
|
339
|
+
},
|
|
340
|
+
tools: {
|
|
341
|
+
requestedBuiltin: toolPlan.requestedBuiltinTools,
|
|
342
|
+
declaredBuiltin: toolPlan.declaredBuiltinTools,
|
|
343
|
+
effectiveAllowlist: toolPlan.effectiveToolAllowlist,
|
|
344
|
+
explicitAllowlist: toolPlan.explicitToolAllowlist,
|
|
345
|
+
requiredChildTools: toolPlan.requiredChildTools,
|
|
346
|
+
internalTools: toolPlan.internalTools,
|
|
347
|
+
mcp: toolPlan.effectiveMcpSelections,
|
|
348
|
+
effectiveMcpTools: toolPlan.effectiveMcpTools,
|
|
349
|
+
toolExtensionPaths: toolPlan.toolExtensionPaths,
|
|
350
|
+
runtimeExtensions: toolPlan.runtimeExtensions,
|
|
351
|
+
configuredExtensions: toolPlan.configuredExtensions,
|
|
352
|
+
extensionArgs: toolPlan.extensionArgs,
|
|
353
|
+
disableAmbientExtensions: toolPlan.disableAmbientExtensions,
|
|
354
|
+
fanoutAuthorized: toolPlan.fanoutAuthorized,
|
|
355
|
+
...(toolPlan.capabilityCeiling ? { capabilityCeiling: toolPlan.capabilityCeiling } : {}),
|
|
356
|
+
...(toolPlan.capabilityAudit ? { capabilityAudit: toolPlan.capabilityAudit } : {}),
|
|
357
|
+
},
|
|
358
|
+
roots: {
|
|
359
|
+
cwd: effectiveCwd,
|
|
360
|
+
...(sessionRoot ? { sessionRoot } : {}),
|
|
361
|
+
...(sessionDir ? { sessionDir, sessionFile: path.join(sessionDir, "session.jsonl") } : {}),
|
|
362
|
+
...(artifactsDir ? { artifactsDir } : {}),
|
|
363
|
+
...(artifactPaths ? { artifactPaths } : {}),
|
|
364
|
+
...(outputPath ? { outputPath } : {}),
|
|
365
|
+
lifecycle: {
|
|
366
|
+
asyncDir: lifecycleAsyncDir,
|
|
367
|
+
resultPath: lifecycleResultPath,
|
|
368
|
+
statusPath: path.join(lifecycleAsyncDir, "status.json"),
|
|
369
|
+
eventsPath: path.join(lifecycleAsyncDir, "events.jsonl"),
|
|
370
|
+
processTerminalPath: processTerminalPath(lifecycleAsyncDir),
|
|
371
|
+
processTerminalCandidatePath: processTerminalCandidatePath(lifecycleAsyncDir),
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
protocol: {
|
|
375
|
+
lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
376
|
+
packageVersion: packageVersion(),
|
|
377
|
+
},
|
|
378
|
+
diagnostics,
|
|
379
|
+
launchContractDigest: launchBindingDigest({
|
|
380
|
+
task: input.task ?? "",
|
|
381
|
+
definitionDigest,
|
|
382
|
+
...(model ? { model } : {}),
|
|
383
|
+
modelCandidates,
|
|
384
|
+
...(resolveEffectiveThinking(model, effectiveThinkingConfig) ? { thinking: resolveEffectiveThinking(model, effectiveThinkingConfig) } : {}),
|
|
385
|
+
systemPrompt: effectiveSystemPrompt,
|
|
386
|
+
systemPromptMode: agent.systemPromptMode,
|
|
387
|
+
inheritProjectContext: agent.inheritProjectContext,
|
|
388
|
+
inheritSkills: agent.inheritSkills,
|
|
389
|
+
skills: requestedSkills,
|
|
390
|
+
tools: toolPlan.effectiveToolAllowlist,
|
|
391
|
+
extensions: toolPlan.extensionArgs,
|
|
392
|
+
mcpDirectTools: toolPlan.effectiveMcpTools,
|
|
393
|
+
...(outputPath ? { outputPath } : {}),
|
|
394
|
+
outputMode: input.outputMode ?? "inline",
|
|
395
|
+
...(input.outputSchema ? { structuredOutputSchema: input.outputSchema } : {}),
|
|
396
|
+
}),
|
|
397
|
+
};
|
|
398
|
+
return { ok: true, contract: { ...contractBase, digest: digestContract(contractBase) } };
|
|
399
|
+
}
|
package/src/extension/index.ts
CHANGED
|
@@ -26,7 +26,7 @@ import { resolveCurrentSessionId } from "../shared/session-identity.ts";
|
|
|
26
26
|
import { cleanupOldChainDirs } from "../shared/settings.ts";
|
|
27
27
|
import { clearLegacyResultAnimationTimer, renderSubagentResult } from "../tui/render.ts";
|
|
28
28
|
import { openSubagentFleet } from "../tui/fleet.ts";
|
|
29
|
-
import { SubagentFleetStatus } from "../tui/fleet-status.ts";
|
|
29
|
+
import { SubagentFleetStatus, resolveFleetViewPlacement } from "../tui/fleet-status.ts";
|
|
30
30
|
import { SubagentParams } from "./schemas.ts";
|
|
31
31
|
import { validateChainInput } from "./chain-validation.ts";
|
|
32
32
|
import { createSubagentExecutor, type SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
@@ -47,6 +47,7 @@ import { drainOutstandingWork } from "../runs/background/auto-drain.ts";
|
|
|
47
47
|
import registerSubagentNotify, { parseSubagentNotifyContent, type SubagentNotifyDetails } from "../runs/background/notify.ts";
|
|
48
48
|
import { formatSteeringNotice, handleSubagentSteeringNotice, SUBAGENT_STEERING_MESSAGE_TYPE, type SubagentSteeringMessageDetails } from "./steering-notices.ts";
|
|
49
49
|
import { SUBAGENT_CHILD_ENV, SUBAGENT_PARENT_SESSION_ENV } from "../runs/shared/pi-args.ts";
|
|
50
|
+
import { resolveCurrentSubagentCapabilityCeiling } from "../runs/shared/capability-ceiling.ts";
|
|
50
51
|
import { formatDuration, shortenPath } from "../shared/formatters.ts";
|
|
51
52
|
import { loadConfig } from "./config.ts";
|
|
52
53
|
import { buildSubagentToolDescription } from "./tool-description.ts";
|
|
@@ -142,10 +143,13 @@ function createSlashResultComponent(
|
|
|
142
143
|
}
|
|
143
144
|
|
|
144
145
|
class SubagentControlNoticeComponent implements Component {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
) {
|
|
146
|
+
private readonly details: SubagentControlMessageDetails;
|
|
147
|
+
private readonly theme: ExtensionContext["ui"]["theme"];
|
|
148
|
+
|
|
149
|
+
constructor(details: SubagentControlMessageDetails, theme: ExtensionContext["ui"]["theme"]) {
|
|
150
|
+
this.details = details;
|
|
151
|
+
this.theme = theme;
|
|
152
|
+
}
|
|
149
153
|
|
|
150
154
|
invalidate(): void {}
|
|
151
155
|
|
|
@@ -192,6 +196,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
192
196
|
const waitToolConfig = resolveWaitToolConfig(config.waitTool);
|
|
193
197
|
const asyncByDefault = config.asyncByDefault === true;
|
|
194
198
|
const fleetViewEnabled = config.fleetView !== false;
|
|
199
|
+
const fleetViewPlacement = resolveFleetViewPlacement(config.fleetViewPlacement);
|
|
195
200
|
const asyncWidgetEnabled = config.asyncWidget === true || (!fleetViewEnabled && config.asyncWidget !== false);
|
|
196
201
|
const tempArtifactsDir = getArtifactsDir(null);
|
|
197
202
|
cleanupAllArtifactDirs(DEFAULT_ARTIFACT_CONFIG.cleanupDays);
|
|
@@ -235,14 +240,17 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
235
240
|
const ctx = state.lastUiContext;
|
|
236
241
|
if (!ctx?.hasUI) return;
|
|
237
242
|
await openSubagentFleet(ctx, state, { initialKey: itemKey });
|
|
238
|
-
})
|
|
243
|
+
}, { placement: fleetViewPlacement })
|
|
239
244
|
: undefined;
|
|
240
245
|
const { startResultWatcher, primeExistingResults, stopResultWatcher } = createResultWatcher(
|
|
241
246
|
pi,
|
|
242
247
|
state,
|
|
243
248
|
RESULTS_DIR,
|
|
244
249
|
10 * 60 * 1000,
|
|
245
|
-
{
|
|
250
|
+
{
|
|
251
|
+
notifier: completionNotifier,
|
|
252
|
+
deliverIntercomResults: config.intercomBridge?.resultDelivery !== false,
|
|
253
|
+
},
|
|
246
254
|
);
|
|
247
255
|
|
|
248
256
|
const runtimeCleanup = () => {
|
|
@@ -277,6 +285,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
277
285
|
}
|
|
278
286
|
return executorExecute(randomUUID(), params, signal, undefined, ctx);
|
|
279
287
|
},
|
|
288
|
+
resolveCapabilityCeiling: (sessionId) => resolveCurrentSubagentCapabilityCeiling(sessionId),
|
|
280
289
|
});
|
|
281
290
|
const executor = createSubagentExecutor({
|
|
282
291
|
pi,
|
package/src/extension/rpc.ts
CHANGED
|
@@ -6,11 +6,14 @@ import { resolveAsyncRunLocation } from "../runs/background/async-resume.ts";
|
|
|
6
6
|
import { deliverStopRequest } from "../runs/background/control-channel.ts";
|
|
7
7
|
import { reconcileAsyncRun } from "../runs/background/stale-run-reconciler.ts";
|
|
8
8
|
import type { SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
9
|
+
import { resolveCurrentSessionId } from "../shared/session-identity.ts";
|
|
9
10
|
import {
|
|
10
11
|
type Details,
|
|
11
12
|
ASYNC_DIR,
|
|
12
13
|
RESULTS_DIR,
|
|
13
14
|
SUBAGENT_ASYNC_COMPLETE_EVENT,
|
|
15
|
+
SUBAGENT_PROCESS_TERMINAL_EVENT,
|
|
16
|
+
SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
14
17
|
} from "../shared/types.ts";
|
|
15
18
|
import { readStatus } from "../shared/utils.ts";
|
|
16
19
|
import { SubagentParams } from "./schemas.ts";
|
|
@@ -21,7 +24,7 @@ export const SUBAGENT_RPC_REQUEST_EVENT = "subagents:rpc:v1:request";
|
|
|
21
24
|
export const SUBAGENT_RPC_READY_EVENT = "subagents:rpc:v1:ready";
|
|
22
25
|
export const SUBAGENT_RPC_REPLY_EVENT_PREFIX = "subagents:rpc:v1:reply:";
|
|
23
26
|
|
|
24
|
-
export const SUBAGENT_RPC_METHODS = ["ping", "status", "spawn", "steer", "interrupt", "stop"] as const;
|
|
27
|
+
export const SUBAGENT_RPC_METHODS = ["ping", "status", "spawn", "steer", "interrupt", "stop", "resume"] as const;
|
|
25
28
|
export type SubagentRpcMethod = typeof SUBAGENT_RPC_METHODS[number];
|
|
26
29
|
|
|
27
30
|
export interface SubagentRpcRequestEnvelope {
|
|
@@ -181,12 +184,15 @@ function pingData(ctx: ExtensionContext | null) {
|
|
|
181
184
|
nonRecoveringSteer: true,
|
|
182
185
|
interrupt: true,
|
|
183
186
|
stop: true,
|
|
187
|
+
resume: true,
|
|
188
|
+
processTerminalProof: { version: 1, lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION },
|
|
184
189
|
},
|
|
185
190
|
events: {
|
|
186
191
|
ready: SUBAGENT_RPC_READY_EVENT,
|
|
187
192
|
request: SUBAGENT_RPC_REQUEST_EVENT,
|
|
188
193
|
replyPrefix: SUBAGENT_RPC_REPLY_EVENT_PREFIX,
|
|
189
194
|
asyncComplete: SUBAGENT_ASYNC_COMPLETE_EVENT,
|
|
195
|
+
processTerminal: SUBAGENT_PROCESS_TERMINAL_EVENT,
|
|
190
196
|
},
|
|
191
197
|
session: sessionData(ctx),
|
|
192
198
|
};
|
|
@@ -234,6 +240,24 @@ function steerParams(params: unknown): SubagentParamsLike {
|
|
|
234
240
|
};
|
|
235
241
|
}
|
|
236
242
|
|
|
243
|
+
function resumeParams(params: unknown): SubagentParamsLike {
|
|
244
|
+
const input = assertRecordParams(params, "resume");
|
|
245
|
+
if (typeof input.message !== "string" || !input.message.trim())
|
|
246
|
+
throw new SubagentRpcError("invalid_params", "RPC resume requires a non-empty message.");
|
|
247
|
+
const target = normalizeTargetParams(input, "resume");
|
|
248
|
+
if (!target.id && !target.runId && !target.dir) throw new SubagentRpcError("invalid_params", "RPC resume requires id, runId, or dir.");
|
|
249
|
+
if (input.output !== undefined && (typeof input.output !== "string" || !input.output.trim()))
|
|
250
|
+
throw new SubagentRpcError("invalid_params", "RPC resume output must be a non-empty path.");
|
|
251
|
+
if (input.outputMode !== undefined && input.outputMode !== "file-only")
|
|
252
|
+
throw new SubagentRpcError("invalid_params", "RPC resume supports only file-only output mode.");
|
|
253
|
+
return {
|
|
254
|
+
action: "resume",
|
|
255
|
+
...target,
|
|
256
|
+
message: input.message.trim(),
|
|
257
|
+
...(typeof input.output === "string" ? { output: input.output.trim(), outputMode: "file-only" } : {}),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
237
261
|
function stopAsyncRun(
|
|
238
262
|
params: unknown,
|
|
239
263
|
options: RegisterSubagentRpcBridgeOptions,
|
|
@@ -253,7 +277,7 @@ function stopAsyncRun(
|
|
|
253
277
|
throw new SubagentRpcError("not_found", "Async run not found or already completed; stop requires a live async run directory.");
|
|
254
278
|
}
|
|
255
279
|
|
|
256
|
-
const currentSessionId = ctx.sessionManager
|
|
280
|
+
const currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
257
281
|
const initialStatus = readStatus(location.asyncDir);
|
|
258
282
|
const initialRunId = initialStatus?.runId ?? location.resolvedId ?? path.basename(location.asyncDir);
|
|
259
283
|
if (!initialStatus) throw new SubagentRpcError("not_found", `Status file not found for async run '${initialRunId}'.`);
|
|
@@ -320,6 +344,9 @@ async function handleRequest(
|
|
|
320
344
|
if (request.method === "stop") {
|
|
321
345
|
return stopAsyncRun(request.params, options, ctx);
|
|
322
346
|
}
|
|
347
|
+
if (request.method === "resume") {
|
|
348
|
+
return executeChecked(options, ctx, request.requestId, request.method, resumeParams(request.params));
|
|
349
|
+
}
|
|
323
350
|
throw new SubagentRpcError("unsupported_method", `Unsupported subagent RPC method: ${String(request.method)}`);
|
|
324
351
|
}
|
|
325
352
|
|
package/src/extension/schemas.ts
CHANGED
|
@@ -67,11 +67,17 @@ const JsonSchemaObject = Type.Unsafe({
|
|
|
67
67
|
|
|
68
68
|
const AcceptanceOverride = Type.Unsafe({
|
|
69
69
|
anyOf: [
|
|
70
|
-
{ type: "string", enum: ["auto", "attested", "checked", "verified"
|
|
70
|
+
{ type: "string", enum: ["auto", "attested", "checked", "verified"] },
|
|
71
|
+
{
|
|
72
|
+
type: "string",
|
|
73
|
+
enum: ["reviewed"],
|
|
74
|
+
deprecated: true,
|
|
75
|
+
description: "Invalid as an explicit policy. Recognized only so preflight can explain that reviewed is an achieved status.",
|
|
76
|
+
},
|
|
71
77
|
{ type: "boolean", enum: [false] },
|
|
72
78
|
{ type: "object", additionalProperties: true },
|
|
73
79
|
],
|
|
74
|
-
description: "Optional acceptance policy. In the current/default contract, omitted means auto-inferred
|
|
80
|
+
description: "Optional acceptance policy. For reviewer/read-only calls, omit acceptance. Evidence levels end at verified; require independent review with acceptance.review.required. The value reviewed is an achieved status and is accepted by the schema only for actionable preflight recovery. In the current/default contract, omitted means auto-inferred. With agentContract.version=1, omitted means not requested and acceptance failures are reported separately from execution.",
|
|
75
81
|
});
|
|
76
82
|
|
|
77
83
|
const AgentContractOverride = Type.Object({
|
|
@@ -284,8 +290,8 @@ const SubagentParamsSchema = Type.Object({
|
|
|
284
290
|
})),
|
|
285
291
|
chainDir: Type.Optional(Type.String({ description: "Persistent chain artifact directory; defaults to user-scoped temp storage." })),
|
|
286
292
|
async: Type.Optional(Type.Boolean({ description: "Run in background (default: false, or per config)" })),
|
|
287
|
-
timeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "
|
|
288
|
-
maxRuntimeMs: Type.Optional(Type.Integer({ minimum: 1, description: "Alias
|
|
293
|
+
timeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "Timeout for foreground and async/background runs; foreground defaults to 30m absent call/agent. Alias maxRuntimeMs." })),
|
|
294
|
+
maxRuntimeMs: Type.Optional(Type.Integer({ minimum: 1, description: "Alias timeoutMs for foreground and async/background runs; foreground defaults to 30m absent call/agent." })),
|
|
289
295
|
turnBudget: Type.Optional(TurnBudgetOverride),
|
|
290
296
|
toolBudget: Type.Optional(ToolBudgetOverride),
|
|
291
297
|
agentScope: Type.Optional(Type.String({ description: "Agent discovery scope: 'user', 'project', or 'both' (default: 'both'; project wins on name collisions)" })),
|
|
@@ -23,7 +23,8 @@ EXECUTION (use exactly ONE mode):
|
|
|
23
23
|
• PARALLEL: { tasks: [{agent,task,count?,output?,reads?,progress?}, ...], concurrency?: number, worktree?: true } - concurrent execution (worktree: isolate each task in a git worktree)
|
|
24
24
|
• Optional context: { context: "fresh" | "fork" } (explicit value overrides every child; when omitted, each requested agent uses its own defaultContext, otherwise "fresh"; inspect agent defaults via { action: "list" })
|
|
25
25
|
• Fork thinking: model strings accept a thinking suffix (provider/model:off|minimal|low|medium|high|xhigh|max). Forking over a parent transcript that carries signed Anthropic thinking blocks forces thinking off only when a child's effective primary or fallback model resolves to the Anthropic provider or anthropic-messages API; unresolved models are treated conservatively. The result notes affected children, including on failures. Use fresh context when an Anthropic child needs thinking.
|
|
26
|
-
• Optional timeout: { timeoutMs } or { maxRuntimeMs } sets a run-level max runtime for foreground and async/background runs
|
|
26
|
+
• Optional timeout: { timeoutMs } or { maxRuntimeMs } sets a run-level max runtime for foreground and async/background runs; foreground defaults to 30 minutes only when neither value nor an agent timeout is provided
|
|
27
|
+
• Acceptance: omit acceptance for reviewer/read-only calls. Evidence levels end at verified. Use acceptance.review.required to require independent review of a writer result. Never request acceptance:"reviewed"; reviewed is achieved only after an independent reviewer result.
|
|
27
28
|
• If { action: "list" } shows proactive skill subagent suggestions, consider a small fresh-context fanout for broad tasks where one of those skills would materially help
|
|
28
29
|
|
|
29
30
|
CHAIN TEMPLATE VARIABLES (use in task strings):
|
|
@@ -79,7 +80,8 @@ export const COMPACT_SUBAGENT_TOOL_DESCRIPTION = `To delegate work, call with {
|
|
|
79
80
|
EXECUTE:
|
|
80
81
|
• Before execution, call { action: "list" }; run only executable/non-disabled configured agents/chains.
|
|
81
82
|
• SINGLE {agent, task?}; PARALLEL {tasks:[{agent,task,count?,output?,reads?,progress?}], concurrency?, worktree?}; CHAIN {chain:[{agent,task?},{parallel:[...]}]}.
|
|
82
|
-
• context can be "fresh" or "fork"; omitted uses each agent defaultContext, otherwise fresh. timeoutMs/maxRuntimeMs apply to foreground and async/background runs.
|
|
83
|
+
• context can be "fresh" or "fork"; omitted uses each agent defaultContext, otherwise fresh. timeoutMs/maxRuntimeMs apply to foreground and async/background runs; foreground defaults to 30 minutes only when neither value nor an agent timeout is provided.
|
|
84
|
+
• Omit acceptance for reviewer/read-only calls. Evidence levels end at verified; use acceptance.review.required for independent writer review. reviewed is an achieved status, never an explicit input.
|
|
83
85
|
• Chain templates may use {task}, {previous}, {chain_dir}, and named outputs. Parallel worktree isolation requires a clean git repo.
|
|
84
86
|
• Chain example: { chain: [{agent:"agent-a", task:"Analyze {task}"}, {parallel: [{agent:"agent-b", task:"Check {previous}", count: 3}]}] }
|
|
85
87
|
• If list shows proactive skill subagent suggestions, use a small fresh-context fanout only when the task is broad enough.
|
|
@@ -32,6 +32,7 @@ Do not use contact_supervisor or intercom for routine completion handoffs. If no
|
|
|
32
32
|
export interface IntercomBridgeState {
|
|
33
33
|
active: boolean;
|
|
34
34
|
mode: IntercomBridgeMode;
|
|
35
|
+
resultDelivery: boolean;
|
|
35
36
|
orchestratorTarget?: string;
|
|
36
37
|
extensionDir: string;
|
|
37
38
|
instruction: string;
|
|
@@ -78,11 +79,12 @@ export function resolveIntercomBridgeMode(value: unknown): IntercomBridgeMode {
|
|
|
78
79
|
|
|
79
80
|
function resolveIntercomBridgeConfig(value: ExtensionConfig["intercomBridge"]): Required<IntercomBridgeConfig> {
|
|
80
81
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
81
|
-
return { mode: "always", instructionFile: "" };
|
|
82
|
+
return { mode: "always", instructionFile: "", resultDelivery: true };
|
|
82
83
|
}
|
|
83
84
|
return {
|
|
84
85
|
mode: resolveIntercomBridgeMode(value.mode),
|
|
85
86
|
instructionFile: typeof value.instructionFile === "string" ? value.instructionFile : "",
|
|
87
|
+
resultDelivery: value.resultDelivery !== false,
|
|
86
88
|
};
|
|
87
89
|
}
|
|
88
90
|
|
|
@@ -146,11 +148,12 @@ export function resolveIntercomBridge(input: ResolveIntercomBridgeInput): Interc
|
|
|
146
148
|
);
|
|
147
149
|
const reason = inactiveReason(mode, input.context, orchestratorTarget);
|
|
148
150
|
if (reason || !orchestratorTarget) {
|
|
149
|
-
return { active: false, mode, extensionDir: NATIVE_INTERCOM_EXTENSION_DIR, instruction: defaultInstruction };
|
|
151
|
+
return { active: false, mode, resultDelivery: config.resultDelivery, extensionDir: NATIVE_INTERCOM_EXTENSION_DIR, instruction: defaultInstruction };
|
|
150
152
|
}
|
|
151
153
|
return {
|
|
152
154
|
active: true,
|
|
153
155
|
mode,
|
|
156
|
+
resultDelivery: config.resultDelivery,
|
|
154
157
|
orchestratorTarget,
|
|
155
158
|
extensionDir: NATIVE_INTERCOM_EXTENSION_DIR,
|
|
156
159
|
instruction: buildIntercomBridgeInstruction(orchestratorTarget, resolveInstructionTemplate(config.instructionFile, settingsDir)),
|