pi-subagents 0.60.0 → 0.62.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 +55 -0
- package/docs/agents.md +7 -3
- package/docs/configuration.md +9 -5
- package/docs/extension-api.md +14 -7
- package/docs/models.md +1 -1
- package/docs/observability.md +1 -1
- package/docs/tool-reference.md +13 -4
- package/docs/workflows.md +14 -13
- package/install.mjs +1 -1
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +6 -4
- package/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
- package/skills/pi-subagents/references/execution-controls.md +41 -11
- package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +4 -4
- package/skills/pi-subagents/references/review-and-validation.md +1 -1
- package/src/agents/agent-management.ts +121 -63
- package/src/agents/agent-serializer.ts +3 -0
- package/src/agents/agents.ts +543 -223
- package/src/agents/runtime-agent-registry.ts +5 -1
- package/src/api/background-work.ts +7 -2
- package/src/api/external-runs.ts +67 -4
- package/src/api/preflight.ts +17 -8
- package/src/api/shared-types.ts +1 -0
- package/src/extension/index.ts +7 -4
- package/src/extension/public-execution.ts +48 -4
- package/src/extension/rpc.ts +62 -4
- package/src/extension/schemas.ts +17 -9
- package/src/extension/tool-description.ts +15 -19
- package/src/runs/background/active-async-capacity.ts +26 -7
- package/src/runs/background/async-execution.ts +98 -51
- package/src/runs/background/async-job-tracker.ts +62 -3
- package/src/runs/background/async-resume.ts +6 -3
- package/src/runs/background/async-status.ts +59 -9
- package/src/runs/background/auto-drain.ts +1 -1
- package/src/runs/background/fleet-view.ts +1 -1
- package/src/runs/background/process-terminal.ts +16 -0
- package/src/runs/background/result-watcher.ts +1 -1
- package/src/runs/background/resume-guidance.ts +1 -1
- package/src/runs/background/run-status.ts +2 -2
- package/src/runs/background/scheduled-runs.ts +63 -6
- package/src/runs/background/steering.ts +4 -1
- package/src/runs/background/subagent-runner.ts +15 -9
- package/src/runs/background/subagent-wait.ts +20 -21
- package/src/runs/background/wait-completions.ts +1 -1
- package/src/runs/background/wait-tool.ts +18 -18
- package/src/runs/foreground/execution.ts +75 -9
- package/src/runs/foreground/subagent-executor.ts +181 -60
- package/src/runs/shared/acceptance.ts +113 -27
- package/src/runs/shared/capability-ceiling.ts +1 -0
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/host-step-status.ts +1 -0
- package/src/runs/shared/model-fallback.ts +61 -17
- package/src/runs/shared/parallel-utils.ts +2 -6
- package/src/runs/shared/permissions.ts +1 -1
- package/src/runs/shared/pi-args.ts +24 -12
- package/src/runs/shared/pi-spawn.ts +69 -35
- package/src/runs/shared/structured-output.ts +33 -6
- package/src/runs/shared/subagent-prompt-runtime.ts +20 -3
- package/src/runs/shared/task-intent.ts +17 -6
- package/src/runs/shared/tool-timeout.ts +1 -1
- package/src/runs/shared/workflow-graph.ts +3 -2
- package/src/shared/atomic-json.ts +3 -1
- package/src/shared/fork-context.ts +0 -12
- package/src/shared/fork-session-cwd.ts +27 -0
- package/src/shared/launch-contract.ts +3 -0
- package/src/shared/types.ts +43 -4
- package/src/shared/workflow-child-permit.ts +91 -0
- package/src/slash/prompt-template-bridge.ts +37 -1
- package/src/slash/slash-commands.ts +18 -26
- package/src/slash/subagents-admin.ts +2 -0
- package/src/tui/render.ts +95 -52
- package/src/workflows/scripted-workflow.ts +153 -4
- package/src/workflows/workflow-child-summary.ts +1 -1
- package/src/workflows/workflow-receipt.ts +41 -4
- package/src/workflows/workflow-resources.ts +150 -0
|
@@ -20,6 +20,7 @@ export interface RuntimeAgentDefinition {
|
|
|
20
20
|
systemPrompt: string;
|
|
21
21
|
aliases?: readonly string[];
|
|
22
22
|
tools?: readonly string[];
|
|
23
|
+
excludeTools?: readonly string[];
|
|
23
24
|
allowNestedSubagents?: boolean;
|
|
24
25
|
mcpDirectTools?: readonly string[];
|
|
25
26
|
model?: string;
|
|
@@ -198,7 +199,7 @@ function validateDefinition(value: unknown): RuntimeAgentDefinition {
|
|
|
198
199
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Runtime agent definition must be an object.");
|
|
199
200
|
const definition = value as Record<string, unknown>;
|
|
200
201
|
const supported = new Set([
|
|
201
|
-
"description", "systemPrompt", "aliases", "tools", "allowNestedSubagents", "mcpDirectTools", "model", "fallbackModels", "thinking",
|
|
202
|
+
"description", "systemPrompt", "aliases", "tools", "excludeTools", "allowNestedSubagents", "mcpDirectTools", "model", "fallbackModels", "thinking",
|
|
202
203
|
"systemPromptMode", "inheritProjectContext", "inheritGlobalContext", "inheritSkills", "defaultContext", "defaultAsync", "defaultTimeoutMs",
|
|
203
204
|
"defaultToolTimeoutMs", "defaultAcceptance", "acceptanceRole", "runner", "skills", "skillPath",
|
|
204
205
|
"extensions", "subagentOnlyExtensions", "mutationTools", "output", "outputMode", "defaultReads", "defaultProgress", "interactive",
|
|
@@ -218,6 +219,7 @@ function validateDefinition(value: unknown): RuntimeAgentDefinition {
|
|
|
218
219
|
if (outputMode !== undefined && outputMode !== "inline" && outputMode !== "file-only") throw new Error("Runtime agent definition outputMode must be 'inline' or 'file-only'.");
|
|
219
220
|
const aliases = validateStringList(definition.aliases, "Runtime agent definition aliases");
|
|
220
221
|
const tools = validateStringList(definition.tools, "Runtime agent definition tools");
|
|
222
|
+
const excludeTools = validateStringList(definition.excludeTools, "Runtime agent definition excludeTools");
|
|
221
223
|
const allowNestedSubagents = validateBoolean(definition.allowNestedSubagents, "Runtime agent definition allowNestedSubagents");
|
|
222
224
|
const mcpDirectTools = validateStringList(definition.mcpDirectTools, "Runtime agent definition mcpDirectTools");
|
|
223
225
|
const model = validateOptionalString(definition.model, "Runtime agent definition model");
|
|
@@ -248,6 +250,7 @@ function validateDefinition(value: unknown): RuntimeAgentDefinition {
|
|
|
248
250
|
systemPrompt: validateString(definition.systemPrompt, "Runtime agent definition systemPrompt", MAX_SYSTEM_PROMPT_LENGTH),
|
|
249
251
|
...(aliases ? { aliases } : {}),
|
|
250
252
|
...(tools ? { tools } : {}),
|
|
253
|
+
...(excludeTools ? { excludeTools } : {}),
|
|
251
254
|
...(allowNestedSubagents !== undefined ? { allowNestedSubagents } : {}),
|
|
252
255
|
...(mcpDirectTools ? { mcpDirectTools } : {}),
|
|
253
256
|
...(model ? { model } : {}),
|
|
@@ -327,6 +330,7 @@ function toAgentConfig(name: string, definition: RuntimeAgentDefinition): AgentC
|
|
|
327
330
|
...(aliases ? { aliases } : {}),
|
|
328
331
|
...(definition.runner !== undefined ? { runner: definition.runner } : {}),
|
|
329
332
|
...(definition.tools !== undefined ? { tools: [...definition.tools] } : {}),
|
|
333
|
+
...(definition.excludeTools !== undefined ? { excludeTools: [...definition.excludeTools] } : {}),
|
|
330
334
|
...(definition.allowNestedSubagents !== undefined ? { allowNestedSubagents: definition.allowNestedSubagents } : {}),
|
|
331
335
|
...(definition.mcpDirectTools !== undefined ? { mcpDirectTools: [...definition.mcpDirectTools] } : {}),
|
|
332
336
|
...(definition.model !== undefined ? { model: definition.model } : {}),
|
|
@@ -22,9 +22,14 @@ export interface BackgroundWorkReconcileContext {
|
|
|
22
22
|
nowMs: number;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
export interface BackgroundWorkListContext {
|
|
26
|
+
sessionId: string;
|
|
27
|
+
nowMs: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
25
30
|
export interface BackgroundWorkProvider {
|
|
26
31
|
name: string;
|
|
27
|
-
listActiveWork(): readonly BackgroundWorkItem[];
|
|
32
|
+
listActiveWork(context?: BackgroundWorkListContext): readonly BackgroundWorkItem[];
|
|
28
33
|
wakeChannels?: readonly string[];
|
|
29
34
|
reconcile?(context: BackgroundWorkReconcileContext): void;
|
|
30
35
|
}
|
|
@@ -171,7 +176,7 @@ export function snapshotBackgroundWork(sessionId: string, nowMs = Date.now()): B
|
|
|
171
176
|
}
|
|
172
177
|
let active: readonly BackgroundWorkItem[];
|
|
173
178
|
try {
|
|
174
|
-
active = provider.listActiveWork();
|
|
179
|
+
active = provider.listActiveWork({ sessionId, nowMs });
|
|
175
180
|
} catch (error) {
|
|
176
181
|
throw new Error(
|
|
177
182
|
`Background-work provider '${provider.name}' listActiveWork failed: ${error instanceof Error ? error.message : String(error)}`,
|
package/src/api/external-runs.ts
CHANGED
|
@@ -48,7 +48,16 @@ interface ExternalRunRegistry {
|
|
|
48
48
|
runs: Map<string, ExternalRun>;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
interface TrustedExternalRunRecord {
|
|
52
|
+
value: unknown;
|
|
53
|
+
normalized: ExternalRun;
|
|
54
|
+
keys: readonly string[];
|
|
55
|
+
values: readonly unknown[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const trustedRecordsByRegistry = new WeakMap<object, Map<string, TrustedExternalRunRecord>>();
|
|
59
|
+
|
|
60
|
+
const RUN_FIELD_NAMES = [
|
|
52
61
|
"id",
|
|
53
62
|
"sessionId",
|
|
54
63
|
"source",
|
|
@@ -61,7 +70,8 @@ const RUN_FIELDS = new Set([
|
|
|
61
70
|
"preview",
|
|
62
71
|
"reportPath",
|
|
63
72
|
"transcriptPath",
|
|
64
|
-
]);
|
|
73
|
+
] as const satisfies readonly (keyof ExternalRun)[];
|
|
74
|
+
const RUN_FIELDS = new Set<string>(RUN_FIELD_NAMES);
|
|
65
75
|
const UPDATE_FIELDS = new Set([...RUN_FIELDS].filter((field) => field !== "id" && field !== "sessionId" && field !== "source"));
|
|
66
76
|
|
|
67
77
|
function registry(): ExternalRunRegistry {
|
|
@@ -79,6 +89,14 @@ function registry(): ExternalRunRegistry {
|
|
|
79
89
|
return candidate as ExternalRunRegistry;
|
|
80
90
|
}
|
|
81
91
|
|
|
92
|
+
function trustedRecords(current: ExternalRunRegistry): Map<string, TrustedExternalRunRecord> {
|
|
93
|
+
const cached = trustedRecordsByRegistry.get(current);
|
|
94
|
+
if (cached) return cached;
|
|
95
|
+
const records = new Map<string, TrustedExternalRunRecord>();
|
|
96
|
+
trustedRecordsByRegistry.set(current, records);
|
|
97
|
+
return records;
|
|
98
|
+
}
|
|
99
|
+
|
|
82
100
|
function inputObject(value: unknown, field: string, allowed: Set<string>): Record<string, unknown> {
|
|
83
101
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${field} must be an object.`);
|
|
84
102
|
const input = value as Record<string, unknown>;
|
|
@@ -150,6 +168,41 @@ function clone(run: ExternalRun): ExternalRun {
|
|
|
150
168
|
return { ...run };
|
|
151
169
|
}
|
|
152
170
|
|
|
171
|
+
function trustedRecord(value: unknown, normalized: ExternalRun): TrustedExternalRunRecord {
|
|
172
|
+
const candidate = value as ExternalRun;
|
|
173
|
+
return {
|
|
174
|
+
value,
|
|
175
|
+
normalized,
|
|
176
|
+
keys: Object.keys(candidate),
|
|
177
|
+
values: RUN_FIELD_NAMES.map((field) => candidate[field]),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function trustedRecordValue(value: unknown, record: TrustedExternalRunRecord | undefined): ExternalRun | undefined {
|
|
182
|
+
if (!record || record.value !== value || !value || typeof value !== "object") return undefined;
|
|
183
|
+
try {
|
|
184
|
+
const candidate = value as ExternalRun;
|
|
185
|
+
const keys = Object.keys(candidate);
|
|
186
|
+
if (keys.length !== record.keys.length || keys.some((key, index) => key !== record.keys[index])) return undefined;
|
|
187
|
+
for (const [index, field] of RUN_FIELD_NAMES.entries()) {
|
|
188
|
+
if (candidate[field] !== record.values[index]) return undefined;
|
|
189
|
+
}
|
|
190
|
+
return record.normalized;
|
|
191
|
+
} catch {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function rememberTrustedRecord(current: ExternalRunRegistry, cacheKey: string, value: unknown, normalized: ExternalRun): void {
|
|
197
|
+
trustedRecords(current).set(cacheKey, trustedRecord(value, normalized));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function normalizeCachedRecord(current: ExternalRunRegistry, cacheKey: string, value: unknown): ExternalRun {
|
|
201
|
+
const run = validateRun(value);
|
|
202
|
+
rememberTrustedRecord(current, cacheKey, value, run);
|
|
203
|
+
return run;
|
|
204
|
+
}
|
|
205
|
+
|
|
153
206
|
/** Register one current-session external job. pi-subagents never controls the job. */
|
|
154
207
|
export function registerExternalRun(input: ExternalRun): ExternalRun {
|
|
155
208
|
const run = validateRun(input);
|
|
@@ -158,6 +211,7 @@ export function registerExternalRun(input: ExternalRun): ExternalRun {
|
|
|
158
211
|
if (current.runs.has(runKey)) throw new Error(`External run '${run.id}' is already registered for session '${run.sessionId}'.`);
|
|
159
212
|
if (current.runs.size >= EXTERNAL_RUN_LIMITS.maxCachedRuns) throw new Error(`External-run registry supports at most ${EXTERNAL_RUN_LIMITS.maxCachedRuns} cached runs.`);
|
|
160
213
|
current.runs.set(runKey, run);
|
|
214
|
+
rememberTrustedRecord(current, runKey, run, run);
|
|
161
215
|
return clone(run);
|
|
162
216
|
}
|
|
163
217
|
|
|
@@ -172,12 +226,17 @@ export function updateExternalRun(sessionId: string, id: string, update: Externa
|
|
|
172
226
|
if (!previous) throw new Error(`External run '${safeId}' is not registered for session '${safeSessionId}'.`);
|
|
173
227
|
const next = validateRun({ ...previous, ...patch });
|
|
174
228
|
current.runs.set(runKey, next);
|
|
229
|
+
rememberTrustedRecord(current, runKey, next, next);
|
|
175
230
|
return clone(next);
|
|
176
231
|
}
|
|
177
232
|
|
|
178
233
|
/** Remove a cached external job. The caller remains responsible for its process and artifacts. */
|
|
179
234
|
export function unregisterExternalRun(sessionId: string, id: string): boolean {
|
|
180
|
-
|
|
235
|
+
const current = registry();
|
|
236
|
+
const runKey = key(identity(sessionId, "External run sessionId", EXTERNAL_RUN_LIMITS.maxSessionIdLength), identity(id, "External run id"));
|
|
237
|
+
const deleted = current.runs.delete(runKey);
|
|
238
|
+
if (deleted) trustedRecords(current).delete(runKey);
|
|
239
|
+
return deleted;
|
|
181
240
|
}
|
|
182
241
|
|
|
183
242
|
function snapshotBytes(runs: readonly ExternalRun[]): number {
|
|
@@ -192,15 +251,19 @@ function getErrorMessage(error: unknown): string {
|
|
|
192
251
|
export function snapshotExternalRuns(sessionId: string, options: ExternalRunSnapshotOptions = {}): readonly ExternalRun[] {
|
|
193
252
|
const safeSessionId = identity(sessionId, "External-run snapshot sessionId", EXTERNAL_RUN_LIMITS.maxSessionIdLength);
|
|
194
253
|
const current = registry();
|
|
254
|
+
const trusted = trustedRecords(current);
|
|
255
|
+
const sessionPrefix = `${safeSessionId}\0`;
|
|
195
256
|
const runs: ExternalRun[] = [];
|
|
196
257
|
for (const [cacheKey, value] of current.runs.entries()) {
|
|
258
|
+
if (typeof cacheKey !== "string" || !cacheKey.startsWith(sessionPrefix)) continue;
|
|
197
259
|
try {
|
|
198
|
-
const run =
|
|
260
|
+
const run = trustedRecordValue(value, trusted.get(cacheKey)) ?? normalizeCachedRecord(current, cacheKey, value);
|
|
199
261
|
if (run.sessionId === safeSessionId) runs.push(run);
|
|
200
262
|
} catch (error) {
|
|
201
263
|
const message = `Malformed cached external run '${cacheKey}': ${getErrorMessage(error)}`;
|
|
202
264
|
if (!options.ignoreMalformed) throw new Error(message, { cause: error instanceof Error ? error : undefined });
|
|
203
265
|
current.runs.delete(cacheKey);
|
|
266
|
+
trusted.delete(cacheKey);
|
|
204
267
|
options.onMalformedRecord?.(message);
|
|
205
268
|
}
|
|
206
269
|
}
|
package/src/api/preflight.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import {
|
|
4
|
+
import { discoverAgentSnapshot, findBlockingAgentDiagnostic, formatUnknownAgentError, resolveAgentName, unknownAgentDiagnosticContext, type AgentConfig, type AgentDiscoveryAllResult, type AgentScope, type AgentSource } from "../agents/agents.ts";
|
|
5
5
|
import { resolveExecutionAgentScope } from "../agents/agent-scope.ts";
|
|
6
6
|
import { buildSkillInjection, normalizeSkillInput, resolveSkillsWithFallback } from "../agents/skills.ts";
|
|
7
7
|
import { buildAgentMemoryInjection } from "../agents/agent-memory.ts";
|
|
8
|
-
import { buildModelCandidates, inheritsParentModel, resolveEffectiveSubagentModel, type AvailableModelInfo, type ParentModel } from "../runs/shared/model-fallback.ts";
|
|
8
|
+
import { buildModelCandidates, inheritsParentModel, resolveEffectiveSubagentModel, resolveModelOrigin, type AvailableModelInfo, type ParentModel } from "../runs/shared/model-fallback.ts";
|
|
9
9
|
import { resolveModelScopesForAgent } from "../runs/shared/model-scope.ts";
|
|
10
10
|
import { applyThinkingSuffix, resolvePiLaunchToolPlan, type PiLaunchToolPlan } from "../runs/shared/pi-args.ts";
|
|
11
11
|
import { injectOutputPathSystemPrompt, normalizeSingleOutputOverride, resolveSingleOutputPath } from "../runs/shared/single-output.ts";
|
|
@@ -111,6 +111,7 @@ export interface SubagentLaunchContractSkills {
|
|
|
111
111
|
export interface SubagentLaunchContractTools {
|
|
112
112
|
requestedBuiltin: string[];
|
|
113
113
|
declaredBuiltin: string[];
|
|
114
|
+
excludeTools?: string[];
|
|
114
115
|
effectiveAllowlist: string[];
|
|
115
116
|
explicitAllowlist: boolean;
|
|
116
117
|
requiredChildTools: string[];
|
|
@@ -222,8 +223,7 @@ function taskWorkspaceScopeAuthorityDiagnostic(task: string | undefined): Subage
|
|
|
222
223
|
};
|
|
223
224
|
}
|
|
224
225
|
|
|
225
|
-
function candidateList(inputAgent: string, selected: AgentConfig | undefined,
|
|
226
|
-
const all = discoverAgentsAll(cwd, provider);
|
|
226
|
+
function candidateList(inputAgent: string, selected: AgentConfig | undefined, all: AgentDiscoveryAllResult): SubagentLaunchContractAgentCandidate[] {
|
|
227
227
|
return [...all.builtin, ...all.package, ...all.user, ...all.project]
|
|
228
228
|
.filter((agent) => Boolean(resolveAgentName(inputAgent, [agent]).agent))
|
|
229
229
|
.map((agent) => ({
|
|
@@ -258,7 +258,8 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
258
258
|
}
|
|
259
259
|
const scope = resolveExecutionAgentScope(input.agentScope);
|
|
260
260
|
const parentProvider = input.preferredProvider ?? input.parentModel?.provider;
|
|
261
|
-
const
|
|
261
|
+
const discovery = discoverAgentSnapshot(effectiveCwd, scope, parentProvider, { includeChains: false });
|
|
262
|
+
const discovered = discovery.effective;
|
|
262
263
|
const resolvedAgent = resolveAgentName(input.agent, discovered.agents);
|
|
263
264
|
const ambiguousCandidates = resolvedAgent.error
|
|
264
265
|
? discovered.agents.filter((agent) => resolveAgentName(input.agent, [agent]).agent)
|
|
@@ -317,9 +318,13 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
317
318
|
const availableModels = normalizeAvailableModels(input.availableModels);
|
|
318
319
|
const preferredProvider = agent.modelProvider ?? input.preferredProvider ?? input.parentModel?.provider;
|
|
319
320
|
const modelScopes = resolveModelScopesForAgent(discovered.modelScope, agent.name, input.parentModel);
|
|
321
|
+
const modelOrigin = resolveModelOrigin({ explicitModel: input.model, agentModel: agent.model, parentModel: input.parentModel });
|
|
320
322
|
const primaryModel = externalRunner
|
|
321
323
|
? undefined
|
|
322
|
-
: resolveEffectiveSubagentModel(input.model, agent.model, input.parentModel, availableModels, preferredProvider, {
|
|
324
|
+
: resolveEffectiveSubagentModel(input.model, agent.model, input.parentModel, availableModels, preferredProvider, {
|
|
325
|
+
scope: modelScopes,
|
|
326
|
+
source: modelOrigin === "explicit" ? "explicit" : "inherited",
|
|
327
|
+
});
|
|
323
328
|
const effectiveThinkingConfig = input.thinking !== undefined ? input.thinking : agent.thinking;
|
|
324
329
|
const thinkingCeiling = externalRunner ? undefined : intersectThinkingCeilings(
|
|
325
330
|
discovered.maxThinking,
|
|
@@ -332,7 +337,8 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
332
337
|
? []
|
|
333
338
|
: buildModelCandidates(primaryModel, agent.fallbackModels, availableModels, preferredProvider, {
|
|
334
339
|
scope: modelScopes,
|
|
335
|
-
primaryModelFromParent: inheritsParentModel(input.model, agent.model, input.parentModel),
|
|
340
|
+
primaryModelFromParent: modelOrigin === "inherited" || inheritsParentModel(input.model, agent.model, input.parentModel),
|
|
341
|
+
origin: modelOrigin,
|
|
336
342
|
})
|
|
337
343
|
.map((candidate) => applyThinkingSuffix(candidate, effectiveThinkingConfig, input.thinking !== undefined) ?? candidate);
|
|
338
344
|
if (!externalRunner) {
|
|
@@ -351,6 +357,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
351
357
|
try {
|
|
352
358
|
toolPlan = resolvePiLaunchToolPlan({
|
|
353
359
|
tools: agent.tools,
|
|
360
|
+
excludeTools: agent.excludeTools,
|
|
354
361
|
allowNestedSubagents: agent.allowNestedSubagents,
|
|
355
362
|
extensions: agent.extensions,
|
|
356
363
|
subagentOnlyExtensions: agent.subagentOnlyExtensions,
|
|
@@ -397,7 +404,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
397
404
|
const memoryInjection = buildAgentMemoryInjection(agent, effectiveCwd);
|
|
398
405
|
if (memoryInjection) effectiveSystemPrompt = effectiveSystemPrompt ? `${effectiveSystemPrompt}\n\n${memoryInjection}` : memoryInjection;
|
|
399
406
|
effectiveSystemPrompt = injectOutputPathSystemPrompt(effectiveSystemPrompt, outputPath, agent);
|
|
400
|
-
const candidates = candidateList(input.agent, agent,
|
|
407
|
+
const candidates = candidateList(input.agent, agent, discovery.all);
|
|
401
408
|
const shadowedCandidates = candidates.filter((candidate) => !candidate.selected);
|
|
402
409
|
const definitionDigest = agentDefinitionDigest(agent);
|
|
403
410
|
const contractBase: Omit<SubagentLaunchContract, "digest"> = {
|
|
@@ -430,6 +437,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
430
437
|
tools: {
|
|
431
438
|
requestedBuiltin: toolPlan.requestedBuiltinTools,
|
|
432
439
|
declaredBuiltin: toolPlan.declaredBuiltinTools,
|
|
440
|
+
...(toolPlan.excludeTools.length > 0 ? { excludeTools: toolPlan.excludeTools } : {}),
|
|
433
441
|
effectiveAllowlist: toolPlan.effectiveToolAllowlist,
|
|
434
442
|
explicitAllowlist: toolPlan.explicitToolAllowlist,
|
|
435
443
|
requiredChildTools: toolPlan.requiredChildTools,
|
|
@@ -480,6 +488,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
480
488
|
inheritSkills: agent.inheritSkills,
|
|
481
489
|
skills: requestedSkills,
|
|
482
490
|
tools: toolPlan.effectiveToolAllowlist,
|
|
491
|
+
...(toolPlan.excludeTools.length > 0 ? { excludeTools: toolPlan.excludeTools } : {}),
|
|
483
492
|
extensions: toolPlan.extensionArgs,
|
|
484
493
|
mcpDirectTools: toolPlan.effectiveMcpTools,
|
|
485
494
|
...(outputPath ? { outputPath } : {}),
|
package/src/api/shared-types.ts
CHANGED
package/src/extension/index.ts
CHANGED
|
@@ -19,7 +19,7 @@ import * as path from "node:path";
|
|
|
19
19
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
20
20
|
import { keyText, type ExtensionAPI, type ExtensionContext, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
21
21
|
import { Box, Container, Spacer, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, type Component } from "@earendil-works/pi-tui";
|
|
22
|
-
import {
|
|
22
|
+
import { discoverAgentSnapshot, discoverAgents, type AgentConfig, type AgentScope } from "../agents/agents.ts";
|
|
23
23
|
import { clearRuntimeAgentsForPi, listRuntimeAgentConfigs, mergeRuntimeAgents } from "../agents/runtime-agent-registry.ts";
|
|
24
24
|
import { registerRuntimeAgentEventListener } from "../agents/runtime-agent-events.ts";
|
|
25
25
|
import { ensureAccessibleDir } from "../shared/accessible-dir.ts";
|
|
@@ -438,6 +438,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
438
438
|
const state: SubagentState = {
|
|
439
439
|
baseCwd: "",
|
|
440
440
|
currentSessionId: null,
|
|
441
|
+
statusProjectionSessionId: null,
|
|
441
442
|
completionOwnerId: currentCompletionOwnerId(),
|
|
442
443
|
artifactDirPreference: config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir,
|
|
443
444
|
...(config.authorityPolicy ? { authorityPolicy: config.authorityPolicy } : {}),
|
|
@@ -525,9 +526,10 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
525
526
|
return missionObserverResultCandidateFiles(DIRS.results).length > 0;
|
|
526
527
|
};
|
|
527
528
|
const discoverAgentsForRuntime = (cwd: string, scope: AgentScope, preferredModelProvider?: string) => {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
const
|
|
529
|
+
if (listRuntimeAgentConfigs(pi).length === 0) return discoverAgents(cwd, scope, preferredModelProvider);
|
|
530
|
+
const snapshot = discoverAgentSnapshot(cwd, scope, preferredModelProvider, { includeChains: false });
|
|
531
|
+
const discovered = snapshot.effective;
|
|
532
|
+
const all = snapshot.all;
|
|
531
533
|
const configuredAgents: AgentConfig[] = [
|
|
532
534
|
...all.builtin,
|
|
533
535
|
...all.package,
|
|
@@ -996,6 +998,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
996
998
|
promptTemplateBridge.dispose();
|
|
997
999
|
state.widgetsSuspended = false;
|
|
998
1000
|
state.currentSessionId = null;
|
|
1001
|
+
state.statusProjectionSessionId = null;
|
|
999
1002
|
state.parentSessionFile = null;
|
|
1000
1003
|
parentSessionEnvValue = null;
|
|
1001
1004
|
if (shuttingDownParentSession && process.env[SUBAGENT_PARENT_SESSION_ENV] === shuttingDownParentSession) {
|
|
@@ -18,8 +18,13 @@ export interface PublicSubagentExecutionParams {
|
|
|
18
18
|
chainDir?: unknown;
|
|
19
19
|
chainName?: unknown;
|
|
20
20
|
config?: unknown;
|
|
21
|
+
workflow?: unknown;
|
|
22
|
+
args?: unknown;
|
|
21
23
|
workflowScript?: unknown;
|
|
22
24
|
workflowScriptPath?: unknown;
|
|
25
|
+
sessionOnly?: unknown;
|
|
26
|
+
globalConcurrencyLimit?: unknown;
|
|
27
|
+
maxSubagentSpawnsPerRun?: unknown;
|
|
23
28
|
preflight?: unknown;
|
|
24
29
|
isolation?: unknown;
|
|
25
30
|
worktree?: unknown;
|
|
@@ -45,20 +50,56 @@ export type PublicSubagentExecutionNormalization<T> =
|
|
|
45
50
|
| { ok: true; params: T }
|
|
46
51
|
| { ok: false; error: string; mode: PublicSubagentExecutionMode };
|
|
47
52
|
|
|
53
|
+
export function validateWorkflowCapacityOverrides(params: PublicSubagentExecutionParams): string | undefined {
|
|
54
|
+
for (const [name, value] of [
|
|
55
|
+
["globalConcurrencyLimit", params.globalConcurrencyLimit],
|
|
56
|
+
["maxSubagentSpawnsPerRun", params.maxSubagentSpawnsPerRun],
|
|
57
|
+
] as const) {
|
|
58
|
+
if (value !== undefined && (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0)) return `${name} must be a positive safe integer.`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
48
62
|
/**
|
|
49
63
|
* Enforce the public execution cutover before requests reach the executor.
|
|
50
64
|
* Internal runs.run children and structured owned delegation bypass this boundary.
|
|
51
65
|
*/
|
|
52
66
|
export function normalizePublicSubagentExecution<T extends PublicSubagentExecutionParams>(params: T): PublicSubagentExecutionNormalization<T> {
|
|
67
|
+
for (const field of ["resource", "resourceProvenance", "workflowResource", "workflowResourceProvenance", "workflowResourcePermit", "resourcePermit", "permit"] as const) {
|
|
68
|
+
if (Object.hasOwn(params, field) && (params as Record<string, unknown>)[field] !== undefined) {
|
|
69
|
+
return { ok: false, error: "Public execution does not accept workflow resource provenance or permit fields.", mode: params.action === undefined ? "workflow" : "management" };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
53
72
|
if (params.workflowScript !== undefined && params.workflowScriptPath !== undefined) {
|
|
54
73
|
return { ok: false, error: "workflowScript and workflowScriptPath are mutually exclusive.", mode: "workflow" };
|
|
55
74
|
}
|
|
56
|
-
const
|
|
75
|
+
const hasNamedWorkflow = params.workflow !== undefined;
|
|
76
|
+
if (hasNamedWorkflow && (typeof params.workflow !== "string" || !params.workflow.trim())) {
|
|
77
|
+
return { ok: false, error: "workflow must be a non-empty named workflow resource.", mode: "workflow" };
|
|
78
|
+
}
|
|
79
|
+
if (hasNamedWorkflow && (params.workflowScript !== undefined || params.workflowScriptPath !== undefined)) {
|
|
80
|
+
return { ok: false, error: "workflow is mutually exclusive with workflowScript and workflowScriptPath.", mode: "workflow" };
|
|
81
|
+
}
|
|
82
|
+
if (!hasNamedWorkflow && params.args !== undefined) {
|
|
83
|
+
return { ok: false, error: "args requires a named workflow resource.", mode: "workflow" };
|
|
84
|
+
}
|
|
85
|
+
const hasWorkflowInput = params.workflowScript !== undefined || params.workflowScriptPath !== undefined || hasNamedWorkflow;
|
|
86
|
+
const hasCapacityOverride = params.globalConcurrencyLimit !== undefined || params.maxSubagentSpawnsPerRun !== undefined;
|
|
87
|
+
if (hasCapacityOverride) {
|
|
88
|
+
const capacityOverrideError = validateWorkflowCapacityOverrides(params);
|
|
89
|
+
if (capacityOverrideError) return { ok: false, error: capacityOverrideError, mode: params.action === undefined ? "workflow" : "management" };
|
|
90
|
+
if (params.action !== undefined || hasNamedWorkflow || (params.workflowScript === undefined && params.workflowScriptPath === undefined)) {
|
|
91
|
+
return { ok: false, error: "Workflow capacity overrides are only supported on top-level workflowScript or workflowScriptPath calls.", mode: params.action === undefined ? "workflow" : "management" };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
57
94
|
if (params.preflight !== undefined && !hasWorkflowInput) {
|
|
58
95
|
return { ok: false, error: "preflight requires workflowScript or workflowScriptPath.", mode: params.action === undefined ? "workflow" : "management" };
|
|
59
96
|
}
|
|
97
|
+
if (params.preflight !== undefined && hasNamedWorkflow && params.workflowScript === undefined && params.workflowScriptPath === undefined) {
|
|
98
|
+
return { ok: false, error: "preflight is not supported with named workflow resources.", mode: "workflow" };
|
|
99
|
+
}
|
|
60
100
|
const hasValidWorkflowInput = (typeof params.workflowScript === "string" && Boolean(params.workflowScript.trim()))
|
|
61
|
-
|| (typeof params.workflowScriptPath === "string" && Boolean(params.workflowScriptPath.trim()))
|
|
101
|
+
|| (typeof params.workflowScriptPath === "string" && Boolean(params.workflowScriptPath.trim()))
|
|
102
|
+
|| (typeof params.workflow === "string" && Boolean(params.workflow.trim()));
|
|
62
103
|
if (params.isolation !== undefined) {
|
|
63
104
|
if (params.isolation !== "none" && params.isolation !== "worktree") {
|
|
64
105
|
return { ok: false, error: "isolation must be 'none' or 'worktree'.", mode: hasWorkflowInput ? "workflow" : "management" };
|
|
@@ -81,6 +122,9 @@ export function normalizePublicSubagentExecution<T extends PublicSubagentExecuti
|
|
|
81
122
|
return { ok: false, error: "action must be a non-empty management/control action, or omit action and use workflowScript.", mode: "management" };
|
|
82
123
|
}
|
|
83
124
|
const normalizedAction = typeof action === "string" ? action.trim() : undefined;
|
|
125
|
+
if (normalizedAction !== undefined && hasNamedWorkflow) {
|
|
126
|
+
return { ok: false, error: "Named workflow resource execution must omit action.", mode: "management" };
|
|
127
|
+
}
|
|
84
128
|
if (params.clarify !== undefined) {
|
|
85
129
|
return { ok: false, error: "Public workflowScript execution does not support clarify UI.", mode: "workflow" };
|
|
86
130
|
}
|
|
@@ -141,7 +185,7 @@ export function normalizePublicSubagentExecution<T extends PublicSubagentExecuti
|
|
|
141
185
|
return { ok: false, error: "step is not a public execution field; use workflowScript for orchestration.", mode: "workflow" };
|
|
142
186
|
}
|
|
143
187
|
if (hasWorkflowInput && (params.agent !== undefined || params.task !== undefined)) {
|
|
144
|
-
return { ok: false, error: "Structured single-child execution cannot be combined with workflowScript or workflowScriptPath.", mode: "workflow" };
|
|
188
|
+
return { ok: false, error: "Structured single-child execution cannot be combined with workflow, workflowScript, or workflowScriptPath.", mode: "workflow" };
|
|
145
189
|
}
|
|
146
190
|
if (params.agent !== undefined || params.task !== undefined) {
|
|
147
191
|
if (typeof params.agent !== "string" || !params.agent.trim()) {
|
|
@@ -160,7 +204,7 @@ export function normalizePublicSubagentExecution<T extends PublicSubagentExecuti
|
|
|
160
204
|
};
|
|
161
205
|
}
|
|
162
206
|
if (!hasValidWorkflowInput) {
|
|
163
|
-
return { ok: false, error: "Execution requires either { agent, task? } for one child or a non-empty workflowScript or workflowScriptPath for orchestration.", mode: "workflow" };
|
|
207
|
+
return { ok: false, error: "Execution requires either { agent, task? } for one child, a named workflow resource, or a non-empty workflowScript or workflowScriptPath for orchestration.", mode: "workflow" };
|
|
164
208
|
}
|
|
165
209
|
return { ok: true, params };
|
|
166
210
|
}
|
package/src/extension/rpc.ts
CHANGED
|
@@ -172,6 +172,8 @@ interface FleetCandidate {
|
|
|
172
172
|
goal?: unknown;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
type StatusRpcParams = Pick<SubagentParamsLike, "id" | "runId" | "dir" | "index" | "view" | "lines">;
|
|
176
|
+
|
|
175
177
|
function buildFleetStatus(
|
|
176
178
|
state: SubagentState | undefined,
|
|
177
179
|
keyState: FleetKeyState,
|
|
@@ -380,8 +382,7 @@ function failIfToolError(result: ToolResultWithError): void {
|
|
|
380
382
|
throw new SubagentRpcError("execution_failed", textFromToolResult(result) || "Subagent RPC execution failed.");
|
|
381
383
|
}
|
|
382
384
|
|
|
383
|
-
function
|
|
384
|
-
const input = assertRecordParams(params, method);
|
|
385
|
+
function normalizeTargetParamsFromRecord(input: Record<string, unknown>): Pick<SubagentParamsLike, "id" | "runId" | "dir" | "index"> {
|
|
385
386
|
const output: Pick<SubagentParamsLike, "id" | "runId" | "dir" | "index"> = {};
|
|
386
387
|
if (input.id !== undefined) output.id = input.id as string;
|
|
387
388
|
if (input.runId !== undefined) output.runId = input.runId as string;
|
|
@@ -390,6 +391,43 @@ function normalizeTargetParams(params: unknown, method: SubagentRpcMethod): Pick
|
|
|
390
391
|
return output;
|
|
391
392
|
}
|
|
392
393
|
|
|
394
|
+
function normalizeTargetParams(params: unknown, method: SubagentRpcMethod): Pick<SubagentParamsLike, "id" | "runId" | "dir" | "index"> {
|
|
395
|
+
return normalizeTargetParamsFromRecord(assertRecordParams(params, method));
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function normalizeStatusParams(params: unknown): StatusRpcParams {
|
|
399
|
+
const input = assertRecordParams(params, "status");
|
|
400
|
+
const output: StatusRpcParams = normalizeTargetParamsFromRecord(input);
|
|
401
|
+
if (input.view !== undefined) output.view = input.view as StatusRpcParams["view"];
|
|
402
|
+
if (input.lines !== undefined) output.lines = input.lines as number;
|
|
403
|
+
return output;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function hasStatusTarget(params: StatusRpcParams): boolean {
|
|
407
|
+
return params.id !== undefined
|
|
408
|
+
|| params.runId !== undefined
|
|
409
|
+
|| params.dir !== undefined
|
|
410
|
+
|| params.index !== undefined
|
|
411
|
+
|| params.view !== undefined
|
|
412
|
+
|| params.lines !== undefined;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function canUseInMemoryStatus(state: SubagentState | undefined, sessionId: string | undefined): state is SubagentState {
|
|
416
|
+
return Boolean(
|
|
417
|
+
state
|
|
418
|
+
&& sessionId
|
|
419
|
+
&& state.currentSessionId === sessionId
|
|
420
|
+
&& state.statusProjectionSessionId === sessionId
|
|
421
|
+
&& state.foregroundControls instanceof Map
|
|
422
|
+
&& state.asyncJobs instanceof Map,
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function inMemoryStatusSummary(fleet: SubagentRpcFleetStatus): string {
|
|
427
|
+
const noun = fleet.totalActive === 1 ? "child" : "children";
|
|
428
|
+
return `In-memory subagent status: ${fleet.totalActive} active ${noun}.`;
|
|
429
|
+
}
|
|
430
|
+
|
|
393
431
|
function sessionData(ctx: ExtensionContext | null): { cwd?: string; sessionId?: string; sessionFile?: string | null } {
|
|
394
432
|
if (!ctx) return {};
|
|
395
433
|
return {
|
|
@@ -405,6 +443,7 @@ function pingData(ctx: ExtensionContext | null) {
|
|
|
405
443
|
methods: [...SUBAGENT_RPC_METHODS],
|
|
406
444
|
capabilities: {
|
|
407
445
|
status: true,
|
|
446
|
+
statusProjection: { version: 1, untargeted: "in-memory-when-ready", targeted: "executor" },
|
|
408
447
|
managementActions: [...SUBAGENT_RPC_MANAGEMENT_ACTIONS],
|
|
409
448
|
fleetStatus: { version: 1 },
|
|
410
449
|
asyncStatusSnapshot: { kind: ASYNC_STATUS_SNAPSHOT_KIND, version: ASYNC_STATUS_SNAPSHOT_VERSION },
|
|
@@ -689,14 +728,33 @@ async function handleRequest(
|
|
|
689
728
|
return executeChecked(options, ctx, request.requestId, request.method, spawnParams(request.params));
|
|
690
729
|
}
|
|
691
730
|
if (request.method === "status") {
|
|
731
|
+
const statusParams = normalizeStatusParams(request.params);
|
|
732
|
+
let sessionId: string | undefined;
|
|
733
|
+
if (!hasStatusTarget(statusParams)) {
|
|
734
|
+
try {
|
|
735
|
+
sessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
736
|
+
} catch {
|
|
737
|
+
// Let the executor produce the canonical error when session identity is unavailable.
|
|
738
|
+
}
|
|
739
|
+
if (canUseInMemoryStatus(options.state, sessionId)) {
|
|
740
|
+
const fleet = buildFleetStatus(options.state, fleetKeys, sessionId);
|
|
741
|
+
const asyncSnapshot = buildAsyncStatusSnapshotForState(options.state, sessionId);
|
|
742
|
+
return {
|
|
743
|
+
text: inMemoryStatusSummary(fleet),
|
|
744
|
+
details: { mode: "management", results: [] },
|
|
745
|
+
fleet,
|
|
746
|
+
asyncSnapshot,
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
}
|
|
692
750
|
const status = await executeChecked(
|
|
693
751
|
options,
|
|
694
752
|
ctx,
|
|
695
753
|
request.requestId,
|
|
696
754
|
request.method,
|
|
697
|
-
{ action: "status", ...
|
|
755
|
+
{ action: "status", ...statusParams },
|
|
698
756
|
);
|
|
699
|
-
|
|
757
|
+
sessionId ??= resolveCurrentSessionId(ctx.sessionManager);
|
|
700
758
|
return {
|
|
701
759
|
...status,
|
|
702
760
|
fleet: buildFleetStatus(
|
package/src/extension/schemas.ts
CHANGED
|
@@ -86,10 +86,13 @@ const AcceptanceOverride = Type.Unsafe({
|
|
|
86
86
|
deprecated: true,
|
|
87
87
|
description: "Invalid as an explicit policy. Recognized only so preflight can explain that reviewed is an achieved status.",
|
|
88
88
|
},
|
|
89
|
+
{
|
|
90
|
+
type: "string",
|
|
91
|
+
},
|
|
89
92
|
{ type: "boolean", enum: [false] },
|
|
90
93
|
{ type: "object", additionalProperties: true },
|
|
91
94
|
],
|
|
92
|
-
description: `Optional acceptance policy.
|
|
95
|
+
description: `Optional acceptance policy. Prefer an inline JSON object. JSON-encoded object strings are tolerated only during input normalization; invalid strings fail closed. Reviewer/read-only calls, omit acceptance. { level: "checked", evidence: ["commands-run", "changed-files"] }. Supported evidence kinds: ${AcceptanceEvidenceKinds.join(",")}. acceptance.review.required.`,
|
|
93
96
|
});
|
|
94
97
|
|
|
95
98
|
const AgentContractOverride = Type.Object({
|
|
@@ -317,8 +320,9 @@ const SubagentParamProperties = {
|
|
|
317
320
|
thinking: Type.Optional(Type.Unsafe({ anyOf: [{ type: "string" }, { type: "boolean", enum: [false] }], description: "Thinking level for action='watchdog.configure' only (off/minimal/low/medium/high/xhigh/max, inherit, or false for off). Ignored on dispatch; set per-run child thinking with a suffix on the model string, e.g. model: 'provider/id:high'." })),
|
|
318
321
|
at: Type.Optional(Type.String({ description: "One-shot trigger for action='schedule.create': a relative delay such as '+10m' or an ISO timestamp with timezone." })),
|
|
319
322
|
every: Type.Optional(Type.String({ description: "Fixed recurring interval for action='schedule.create', such as '30m', '6h', '2d', or '2w'." })),
|
|
323
|
+
sessionOnly: Type.Optional(Type.Boolean()),
|
|
320
324
|
on: Type.Optional(Type.Unsafe({ anyOf: [{ type: "string" }, { type: "integer" }], description: "Calendar selector reserved for a later schedule slice." })),
|
|
321
|
-
timezone: Type.Optional(Type.String(
|
|
325
|
+
timezone: Type.Optional(Type.String()),
|
|
322
326
|
overlap: Type.Optional(Type.String({ enum: ["skip"], description: "Overlap policy. This slice supports skip only." })),
|
|
323
327
|
catchUp: Type.Optional(Type.String({ enum: ["none", "latest"], description: "Missed occurrence policy for recurring schedules. Defaults to latest." })),
|
|
324
328
|
missionId: Type.Optional(Type.String({ description: "Mission id." })),
|
|
@@ -337,8 +341,12 @@ const SubagentParamProperties = {
|
|
|
337
341
|
],
|
|
338
342
|
description: "Agent config for create/update. Object or JSON string."
|
|
339
343
|
})),
|
|
340
|
-
|
|
341
|
-
|
|
344
|
+
workflow: Type.Optional(Type.String({ minLength: 1, description: "Extension-owned workflow resource; resolves its script and authority internally." })),
|
|
345
|
+
args: Type.Optional(Type.Unsafe({ type: "object", maxProperties: 16, additionalProperties: true, description: "Bounded plain-JSON args for workflow; resource validation applies." })),
|
|
346
|
+
workflowScript: Type.Optional(Type.String({ minLength: 1, description: "Inline JavaScript statement body with unknown resource provenance. Normally async unless asyncByDefault:false; set async:true when async matters. Use async:false only when the parent must block until completion, never for reviews or gates. Use explicit return for output. Use top-level await, plain helper functions, or explicit Promise chains; nested async function, arrow, and method helpers are rejected. Use await runs.run(key, {agent, task, worktree?, gate?}) or runs.run(key, {resume, task}), where resume is a retained run id or {workflowRunId,key,latest:true} from a durable async workflow receipt. Each workflow key identifies one result lane: use a new stable workflow key for every distinct retained resume pass; same-key calls are reused only when launch parameters are identical, and incompatible parameters are rejected. Use runs.all([...]), runs.host(key,{kind:'command',command,timeoutMs,output?,role?,provider?}), await runs.steer(key, message, {mode?, index?, ackTimeoutMs?}), runs.status(id), runs.ref(s), emit(value), console, and return. For bounded parallel sequential chains, use runs.lanes([{key,stages:[{key,agent,task},{key,resume:'previous',task},...]}]); first stages run together, later stages sequence per lane, and the bounded board reports lane-local failures. Only an explicit structuredOutput.verdict === 'blocked' blocks a successful stage; reviewer prose is not parsed. For ordinary parallel fanout, use await runs.all([{key, agent, task}, ...]); it resolves to an ordered array, not a key map, so use results[0], destructuring, or results.map(...), not results.<key>. Do not read .output from unawaited runs.run launches. Stored runs.run promises are only for advanced rolling fanout, and each must later be observed with direct await, Promise.race, or Promise.all. runs.steer targets a prior stable child key, never a raw run id, and must be awaited or returned. Mission workflows also have async state.get(key) and state.set(key, JSONValue). Compose sequential and parallel phases dynamically. Set worktree:true at workflow or child level for a separate managed worktree; child fields override workflow defaults. gate is one host-run command and cannot be combined with acceptance. runs.run accepts one child only. No filesystem, shell, Pi tools, or host globals except through runs.host." })),
|
|
347
|
+
workflowScriptPath: Type.Optional(Type.String({ minLength: 1, description: "Path to a JavaScript workflow file with unknown resource provenance. Mutually exclusive with workflowScript and workflow. Relative paths resolve against the request cwd. The host reads the file before the filesystem-free workflow sandbox starts." })),
|
|
348
|
+
globalConcurrencyLimit: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
349
|
+
maxSubagentSpawnsPerRun: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
342
350
|
preflight: Type.Optional(WorkflowPreflightOverride),
|
|
343
351
|
chatProgress: Type.Optional(Type.String({ enum: ["auto", "off", "live-card"], description: "WorkflowScript chat progress projection. auto shows a live in-chat card only for watched foreground workflows in the same Git repository; it is off otherwise. Explicit live-card requires same-repository async:false; async workflows should omit chatProgress or use auto/off." })),
|
|
344
352
|
isolation: Type.Optional(Type.String({ enum: ["none", "worktree"], description: "Workflow child isolation. none runs in the shared cwd; worktree requires managed git worktree isolation." })),
|
|
@@ -391,20 +399,20 @@ export function createSubagentParamsSchema(): typeof SubagentParams {
|
|
|
391
399
|
|
|
392
400
|
const SubagentWaitParamsSchema = Type.Object({
|
|
393
401
|
id: Type.Optional(Type.String({
|
|
394
|
-
description: "Async run or remembered detached foreground run id/prefix to wait for one specific run. Omit to wait across every active async run started in this session.",
|
|
402
|
+
description: "Async run or remembered detached foreground run id/prefix to wait for one specific run. Ordinary async subagent runs already notify this session natively; use bg_wait for provider, detached, or other background work without native notification, or when same-turn blocking results are truly needed. Omit to wait across every active async run started in this session only when a same-turn wait is truly needed.",
|
|
395
403
|
})),
|
|
396
404
|
nonBlocking: Type.Optional(Type.Boolean({
|
|
397
|
-
description: "When true, resolve id to one exact run, persist a wake subscription, and return immediately. The originating session is woken on completion, failure, attention, reconciliation failure, or timeout. Requires id and cannot be combined with all.",
|
|
405
|
+
description: "When true, resolve id to one exact run, persist a wake subscription, and return immediately. Use this only for provider, detached, or other background work without a native completion notification; ordinary async subagent runs already notify this session natively and do not need a subscription. The originating session is woken on completion, failure, attention, reconciliation failure, or timeout. Requires id and cannot be combined with all.",
|
|
398
406
|
})),
|
|
399
407
|
all: Type.Optional(Type.Boolean({
|
|
400
|
-
description: "Wait for ALL active runs to finish.
|
|
408
|
+
description: "Wait for ALL active runs to finish. Ordinary async subagent runs already notify this session natively; use all only when a same-turn result from tracked background work is truly needed. Default false: return when the first tracked run or provider item finishes or needs attention. Ignored when id targets a single run.",
|
|
401
409
|
})),
|
|
402
410
|
timeoutMs: Type.Optional(Type.Integer({
|
|
403
411
|
minimum: 1,
|
|
404
|
-
description: "Give up waiting after this many milliseconds (the runs keep going regardless). Defaults to config waitTool.defaultTimeoutMs, then 1800000 (30 minutes). Window expiry is a non-error active-work result.",
|
|
412
|
+
description: "Give up waiting after this many milliseconds (the runs keep going regardless). Ordinary async subagent runs already notify this session natively; use a wait timeout only when same-turn results are truly needed for provider, detached, or other background work without native notification. Defaults to config waitTool.defaultTimeoutMs, then 1800000 (30 minutes). Window expiry is a non-error active-work result.",
|
|
405
413
|
})),
|
|
406
414
|
stopOnAttention: Type.Optional(Type.Boolean({
|
|
407
|
-
description: "
|
|
415
|
+
description: "For a blocking wait that is truly needed, stop when a run needs attention by default. Set false to keep waiting through idle or long-thinking attention; supervisor/contact requests still stop the wait.",
|
|
408
416
|
})),
|
|
409
417
|
});
|
|
410
418
|
|