pi-subagents 0.55.0 → 0.57.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 +81 -3
- package/agents/claude-code-writer.md +15 -0
- package/agents/claude-code.md +15 -0
- package/agents/codex-exec-writer.md +15 -0
- package/agents/codex-exec.md +15 -0
- package/agents/cursor-agent-writer.md +14 -0
- package/agents/cursor-agent.md +14 -0
- package/docs/agents.md +111 -15
- package/docs/configuration.md +37 -0
- package/docs/extension-api.md +2 -2
- package/docs/models.md +6 -0
- package/docs/observability.md +8 -7
- package/docs/tool-reference.md +11 -1
- package/docs/workflows.md +20 -0
- package/package.json +1 -1
- package/skills/pi-subagents/references/execution-controls.md +1 -1
- package/src/agents/agent-management.ts +13 -5
- package/src/agents/agent-refinements.ts +4 -4
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +242 -49
- package/src/agents/builtin-names.ts +6 -0
- package/src/agents/runtime-agent-registry.ts +8 -2
- package/src/api/preflight.ts +22 -3
- package/src/extension/config.ts +70 -0
- package/src/extension/doctor.ts +3 -3
- package/src/extension/index.ts +18 -4
- package/src/extension/public-execution.ts +29 -13
- package/src/extension/rpc.ts +20 -5
- package/src/extension/schemas.ts +9 -3
- package/src/extension/tool-description.ts +11 -9
- package/src/inspectors/herdr/actions.ts +2 -1
- package/src/inspectors/herdr/inspector-runner.ts +2 -10
- package/src/inspectors/herdr/session-roots-codec.ts +42 -0
- package/src/integrations/herdr-status.ts +46 -3
- package/src/runs/background/active-async-capacity.ts +77 -10
- package/src/runs/background/async-execution.ts +148 -27
- package/src/runs/background/async-job-tracker.ts +5 -0
- package/src/runs/background/async-resume.ts +3 -0
- package/src/runs/background/async-retention.ts +20 -3
- package/src/runs/background/async-status.ts +5 -0
- package/src/runs/background/chain-root-attachment.ts +15 -1
- package/src/runs/background/fleet-view.ts +16 -10
- package/src/runs/background/inspect-rpc.ts +8 -8
- package/src/runs/background/result-files.ts +27 -14
- package/src/runs/background/result-watcher.ts +9 -5
- package/src/runs/background/run-status.ts +33 -6
- package/src/runs/background/scheduled-runs.ts +7 -1
- package/src/runs/background/subagent-runner.ts +254 -49
- package/src/runs/background/wait-completions.ts +4 -0
- package/src/runs/foreground/execution.ts +107 -16
- package/src/runs/foreground/foreground-control.ts +6 -0
- package/src/runs/foreground/foreground-history.ts +25 -3
- package/src/runs/foreground/subagent-executor.ts +324 -84
- package/src/runs/foreground/workflow-detach-reconcile.ts +31 -7
- package/src/runs/shared/acceptance.ts +10 -5
- package/src/runs/shared/agent-contract.ts +1 -1
- package/src/runs/shared/child-protocol.ts +21 -7
- package/src/runs/shared/claude-code-adapter.ts +129 -0
- package/src/runs/shared/codex-exec-adapter.ts +129 -0
- package/src/runs/shared/completion-guard.ts +3 -2
- package/src/runs/shared/cursor-agent-adapter.ts +114 -0
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/extension-bindings.ts +78 -0
- package/src/runs/shared/external-cli-contract.ts +167 -0
- package/src/runs/shared/external-cli-preflight.ts +122 -0
- package/src/runs/shared/external-cli-runner.ts +349 -54
- package/src/runs/shared/fast-mode-extension.ts +10 -0
- package/src/runs/shared/model-exclusions.ts +59 -8
- package/src/runs/shared/model-fallback.ts +25 -8
- package/src/runs/shared/mutation-evidence.ts +150 -0
- package/src/runs/shared/nested-events.ts +3 -1
- package/src/runs/shared/nested-render.ts +2 -2
- package/src/runs/shared/parallel-utils.ts +3 -0
- package/src/runs/shared/pi-args.ts +47 -0
- package/src/runs/shared/process-signal.ts +13 -0
- package/src/runs/shared/run-history.ts +21 -1
- package/src/runs/shared/structured-output.ts +18 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +9 -5
- package/src/shared/fork-context.ts +21 -0
- package/src/shared/formatters.ts +7 -1
- package/src/shared/launch-contract.ts +6 -0
- package/src/shared/pruned-fork.ts +450 -0
- package/src/shared/session-file-trust.ts +19 -0
- package/src/shared/session-tokens.ts +14 -3
- package/src/shared/settings.ts +16 -3
- package/src/shared/shortcuts.ts +17 -0
- package/src/shared/types.ts +166 -9
- package/src/shared/workflow-child-permit.ts +116 -0
- package/src/slash/delegation-adapters.ts +0 -1
- package/src/slash/slash-commands.ts +8 -6
- package/src/tui/fleet-status.ts +27 -10
- package/src/tui/fleet-transcript.ts +11 -5
- package/src/tui/fleet.ts +28 -13
- package/src/tui/render.ts +47 -21
- package/src/workflows/scripted-workflow.ts +298 -30
- package/src/workflows/workflow-child-summary.ts +117 -0
- package/src/workflows/workflow-receipt.ts +119 -4
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { AcceptanceInput, AcceptanceRole, AgentRunnerConfig, OutputMode, ToolBudgetConfig, TurnBudgetConfig } from "../shared/types.ts";
|
|
3
|
+
import { CODE_OWNED_EXTERNAL_CLI_ADAPTER_LABEL, isCodeOwnedExternalCliAdapterId, parseExternalCliCapabilityNarrowing, validateCodeOwnedProfileRunner } from "../runs/shared/external-cli-contract.ts";
|
|
3
4
|
import { validateAcceptanceInput } from "../runs/shared/acceptance.ts";
|
|
4
5
|
import { validatePermissionRules, type PermissionRules } from "../runs/shared/permissions.ts";
|
|
5
6
|
import { validateToolBudgetConfig } from "../runs/shared/tool-budget.ts";
|
|
@@ -169,12 +170,15 @@ function validateRunner(value: unknown): AgentRunnerConfig | undefined {
|
|
|
169
170
|
if (runner.type !== "external-cli") throw new Error("Runtime agent definition runner.type must be 'pi', 'external-cli', or 'external-job'.");
|
|
170
171
|
if (typeof runner.command !== "string" || !runner.command.trim()) throw new Error("Runtime agent definition external-cli runner requires a non-empty command string.");
|
|
171
172
|
if (runner.args !== undefined && (!Array.isArray(runner.args) || runner.args.some((arg) => typeof arg !== "string"))) throw new Error("Runtime agent definition external-cli runner args must be an array of strings.");
|
|
173
|
+
if (runner.adapter !== undefined && !isCodeOwnedExternalCliAdapterId(runner.adapter)) throw new Error(`Runtime agent definition external-cli runner adapter must be ${CODE_OWNED_EXTERNAL_CLI_ADAPTER_LABEL}.`);
|
|
174
|
+
if (runner.adapter !== undefined && Array.isArray(runner.args) && runner.args.length > 0) throw new Error(`Runtime agent definition ${runner.adapter} adapter owns its argv; runner args are not supported.`);
|
|
172
175
|
if (runner.promptDelivery !== undefined && runner.promptDelivery !== "stdin") throw new Error("Runtime agent definition external-cli runner promptDelivery must be 'stdin'.");
|
|
173
|
-
const
|
|
176
|
+
const capabilities = parseExternalCliCapabilityNarrowing(runner.capabilities, "Runtime agent definition external-cli runner capabilities");
|
|
177
|
+
const supported = new Set(["type", "adapter", "command", "args", "promptDelivery", "capabilities"]);
|
|
174
178
|
const unknown = Object.keys(runner).filter((key) => !supported.has(key));
|
|
175
179
|
if (unknown.length > 0) throw new Error(`Runtime agent definition external-cli runner has unsupported fields: ${unknown.join(", ")}.`);
|
|
176
180
|
const args = runner.args as string[] | undefined;
|
|
177
|
-
return { type: "external-cli", command: runner.command.trim(), ...(args?.length ? { args } : {}), ...(runner.promptDelivery ? { promptDelivery: "stdin" as const } : {}) };
|
|
181
|
+
return { type: "external-cli", ...(isCodeOwnedExternalCliAdapterId(runner.adapter) ? { adapter: runner.adapter } : {}), command: runner.command.trim(), ...(args?.length ? { args } : {}), ...(runner.promptDelivery ? { promptDelivery: "stdin" as const } : {}), ...(capabilities ? { capabilities } : {}) };
|
|
178
182
|
}
|
|
179
183
|
|
|
180
184
|
function validateTurnBudget(value: unknown): TurnBudgetConfig | undefined {
|
|
@@ -364,6 +368,8 @@ export function registerRuntimeAgent(input: RegisterRuntimeAgentInput): RuntimeA
|
|
|
364
368
|
const name = validateString(input.name, "Runtime agent name", MAX_AGENT_NAME_LENGTH);
|
|
365
369
|
const definition = validateDefinition(input.definition);
|
|
366
370
|
const agent = toAgentConfig(name, definition);
|
|
371
|
+
const profileError = validateCodeOwnedProfileRunner(agent);
|
|
372
|
+
if (profileError) throw new Error(profileError);
|
|
367
373
|
assertNoBuiltinCollision(agent);
|
|
368
374
|
const current = registry();
|
|
369
375
|
const records = current.byPi.get(pi) ?? [];
|
package/src/api/preflight.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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 { discoverAgents, discoverAgentsAll, findBlockingAgentDiagnostic, resolveAgentName, type AgentConfig, type AgentScope, type AgentSource } from "../agents/agents.ts";
|
|
4
|
+
import { discoverAgents, discoverAgentsAll, findBlockingAgentDiagnostic, formatUnknownAgentError, resolveAgentName, unknownAgentDiagnosticContext, type AgentConfig, 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";
|
|
@@ -26,6 +26,7 @@ import { DIRS, TEMP_ROOT_DIR } from "../shared/types.ts";
|
|
|
26
26
|
import { processTerminalCandidatePath, processTerminalPath } from "../runs/background/process-terminal.ts";
|
|
27
27
|
import { resultFilePath } from "../runs/background/result-files.ts";
|
|
28
28
|
import { nestedResultsPath } from "../runs/shared/nested-events.ts";
|
|
29
|
+
import { normalizeExtensionBindings, type ExtensionBindings } from "../runs/shared/extension-bindings.ts";
|
|
29
30
|
|
|
30
31
|
export const SUBAGENT_LAUNCH_CONTRACT_VERSION = 2 as const;
|
|
31
32
|
|
|
@@ -38,7 +39,8 @@ export type SubagentLaunchContractReasonCode =
|
|
|
38
39
|
| "invalid_cwd"
|
|
39
40
|
| "unsupported_mode"
|
|
40
41
|
| "restricted_agent"
|
|
41
|
-
| "thinking_ceiling"
|
|
42
|
+
| "thinking_ceiling"
|
|
43
|
+
| "invalid_extension_bindings";
|
|
42
44
|
|
|
43
45
|
export interface SubagentLaunchContractDiagnostic {
|
|
44
46
|
code: SubagentLaunchContractReasonCode | "host_required" | "snapshot_warning";
|
|
@@ -53,6 +55,7 @@ export interface SubagentLaunchContractInput {
|
|
|
53
55
|
agentScope?: AgentScope;
|
|
54
56
|
context?: "fresh" | "fork";
|
|
55
57
|
model?: string;
|
|
58
|
+
fast?: boolean;
|
|
56
59
|
thinking?: string | false;
|
|
57
60
|
thinkingCeiling?: ThinkingLevel;
|
|
58
61
|
inheritedThinkingCeiling?: ThinkingLevel;
|
|
@@ -63,6 +66,7 @@ export interface SubagentLaunchContractInput {
|
|
|
63
66
|
output?: string | boolean;
|
|
64
67
|
outputMode?: OutputMode;
|
|
65
68
|
outputSchema?: JsonSchemaObject;
|
|
69
|
+
extensionBindings?: ExtensionBindings;
|
|
66
70
|
turnBudget?: ResolvedTurnBudget;
|
|
67
71
|
artifacts?: boolean;
|
|
68
72
|
artifactDir?: ArtifactDirPreference;
|
|
@@ -247,9 +251,18 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
247
251
|
return { ok: false, code: "ambiguous_agent", message: resolvedAgent.error, diagnostics };
|
|
248
252
|
}
|
|
249
253
|
if (!resolvedAgent.agent) {
|
|
250
|
-
return { ok: false, code: "missing_agent", message:
|
|
254
|
+
return { ok: false, code: "missing_agent", message: formatUnknownAgentError(input.agent, unknownAgentDiagnosticContext(discovered)), diagnostics };
|
|
251
255
|
}
|
|
252
256
|
const agent = resolvedAgent.agent;
|
|
257
|
+
let extensionBindings: ExtensionBindings | undefined;
|
|
258
|
+
try {
|
|
259
|
+
extensionBindings = normalizeExtensionBindings(input.extensionBindings)?.value;
|
|
260
|
+
} catch (error) {
|
|
261
|
+
return { ok: false, code: "invalid_extension_bindings", message: error instanceof Error ? error.message : String(error), diagnostics };
|
|
262
|
+
}
|
|
263
|
+
if (extensionBindings !== undefined && (agent.runner?.type === "external-cli" || agent.runner?.type === "external-job")) {
|
|
264
|
+
return { ok: false, code: "unsupported_mode", message: `extensionBindings is not supported for runner.type='${agent.runner.type}'.`, diagnostics };
|
|
265
|
+
}
|
|
253
266
|
const context = resolveLaunchContractContext(input, agent);
|
|
254
267
|
if (context === "fork") {
|
|
255
268
|
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." });
|
|
@@ -313,6 +326,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
313
326
|
}
|
|
314
327
|
let toolPlan: PiLaunchToolPlan;
|
|
315
328
|
const permissionRules = resolvePermissionRules(loadConfig().permissions, agent.permissions);
|
|
329
|
+
const fast = input.fast ?? agent.fast;
|
|
316
330
|
try {
|
|
317
331
|
toolPlan = resolvePiLaunchToolPlan({
|
|
318
332
|
tools: agent.tools,
|
|
@@ -322,6 +336,9 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
322
336
|
cwd: effectiveCwd,
|
|
323
337
|
requireReadTool: resolvedSkills.resolved.length > 0,
|
|
324
338
|
structuredOutput: Boolean(input.outputSchema),
|
|
339
|
+
fast,
|
|
340
|
+
model,
|
|
341
|
+
modelCandidates,
|
|
325
342
|
capabilityCeiling: effectiveCapabilityCeiling,
|
|
326
343
|
agentName: agent.name,
|
|
327
344
|
permissionRules,
|
|
@@ -433,6 +450,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
433
450
|
definitionDigest,
|
|
434
451
|
...(model ? { model } : {}),
|
|
435
452
|
modelCandidates,
|
|
453
|
+
...(fast !== undefined ? { fast } : {}),
|
|
436
454
|
...(resolveEffectiveThinking(model, effectiveThinkingConfig) ? { thinking: resolveEffectiveThinking(model, effectiveThinkingConfig) } : {}),
|
|
437
455
|
systemPrompt: effectiveSystemPrompt,
|
|
438
456
|
systemPromptMode: agent.systemPromptMode,
|
|
@@ -445,6 +463,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
445
463
|
...(outputPath ? { outputPath } : {}),
|
|
446
464
|
outputMode: behavior.outputMode,
|
|
447
465
|
...(input.outputSchema ? { structuredOutputSchema: input.outputSchema } : {}),
|
|
466
|
+
...(extensionBindings ? { extensionBindings } : {}),
|
|
448
467
|
}),
|
|
449
468
|
};
|
|
450
469
|
return { ok: true, contract: { ...contractBase, digest: digestContract(contractBase) } };
|
package/src/extension/config.ts
CHANGED
|
@@ -6,7 +6,9 @@ import { FLEET_KEYBINDING_ACTIONS, type ArtifactDirPreference, type ExtensionCon
|
|
|
6
6
|
import { validateMissionStoreConfig } from "../missions/store.ts";
|
|
7
7
|
import { validateAuthorityPolicy } from "../policy/authority.ts";
|
|
8
8
|
import { getAgentDir } from "../shared/utils.ts";
|
|
9
|
+
import { DEFAULT_MODEL_EXCLUSION_TTL_MS, MAX_MODEL_EXCLUSION_TTL_MS, setDefaultTTL } from "../runs/shared/model-exclusions.ts";
|
|
9
10
|
import { validatePermissionConfig } from "../runs/shared/permissions.ts";
|
|
11
|
+
import { MAX_ABANDONED_SLOT_RELEASE_AFTER_MS, MIN_ABANDONED_SLOT_RELEASE_AFTER_MS } from "../runs/background/active-async-capacity.ts";
|
|
10
12
|
|
|
11
13
|
const ARTIFACT_DIR_PREFERENCES = new Set<ArtifactDirPreference>(["project", "session", "temp"]);
|
|
12
14
|
const FLEET_KEYBINDING_ACTION_SET = new Set<string>(FLEET_KEYBINDING_ACTIONS);
|
|
@@ -16,6 +18,23 @@ const BASE_KEY_IDS = new Set([
|
|
|
16
18
|
...Object.values(Key).flatMap((value) => typeof value === "string" ? [value.toLowerCase()] : []),
|
|
17
19
|
]);
|
|
18
20
|
|
|
21
|
+
class PrunedForkConfigError extends Error {}
|
|
22
|
+
|
|
23
|
+
function validateForkContextConfig(value: unknown): void {
|
|
24
|
+
if (value === undefined) return;
|
|
25
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("config.forkContext must be a JSON object");
|
|
26
|
+
const config = value as Record<string, unknown>;
|
|
27
|
+
if (config.mode !== undefined && config.mode !== "full" && config.mode !== "pruned") {
|
|
28
|
+
throw new Error('config.forkContext.mode must be "full" or "pruned"');
|
|
29
|
+
}
|
|
30
|
+
if (config.model !== undefined && (typeof config.model !== "string" || !config.model.trim())) {
|
|
31
|
+
throw new PrunedForkConfigError("config.forkContext.model must be a non-empty string");
|
|
32
|
+
}
|
|
33
|
+
if (config.mode === "pruned" && config.model === undefined) {
|
|
34
|
+
throw new PrunedForkConfigError('config.forkContext.model is required when config.forkContext.mode is "pruned"');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
19
38
|
function isValidKeyId(value: string): boolean {
|
|
20
39
|
if (value !== value.trim()) return false;
|
|
21
40
|
const parts = value.toLowerCase().split("+");
|
|
@@ -62,6 +81,31 @@ function validateArtifactConfig(value: unknown): void {
|
|
|
62
81
|
}
|
|
63
82
|
}
|
|
64
83
|
|
|
84
|
+
function validateCapacityConfig(value: unknown): void {
|
|
85
|
+
if (value === undefined) return;
|
|
86
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("config.capacity must be a JSON object");
|
|
87
|
+
const abandonedSlotReleaseAfterMs = (value as Record<string, unknown>).abandonedSlotReleaseAfterMs;
|
|
88
|
+
if (abandonedSlotReleaseAfterMs !== undefined
|
|
89
|
+
&& abandonedSlotReleaseAfterMs !== false
|
|
90
|
+
&& (typeof abandonedSlotReleaseAfterMs !== "number"
|
|
91
|
+
|| !Number.isInteger(abandonedSlotReleaseAfterMs)
|
|
92
|
+
|| abandonedSlotReleaseAfterMs < MIN_ABANDONED_SLOT_RELEASE_AFTER_MS
|
|
93
|
+
|| abandonedSlotReleaseAfterMs > MAX_ABANDONED_SLOT_RELEASE_AFTER_MS)) {
|
|
94
|
+
throw new Error(`config.capacity.abandonedSlotReleaseAfterMs must be false or an integer from ${MIN_ABANDONED_SLOT_RELEASE_AFTER_MS} to ${MAX_ABANDONED_SLOT_RELEASE_AFTER_MS}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Validate the user-controlled TTL policy before it reaches the exclusion store. */
|
|
99
|
+
// TEST:test/unit/pi-coding-agent-dir.test.ts[loads and applies model exclusion TTL config]
|
|
100
|
+
function validateModelExclusionsConfig(value: unknown): void {
|
|
101
|
+
if (value === undefined) return;
|
|
102
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("config.modelExclusions must be a JSON object");
|
|
103
|
+
const defaultTtlMs = (value as Record<string, unknown>).defaultTtlMs;
|
|
104
|
+
if (defaultTtlMs !== undefined && (typeof defaultTtlMs !== "number" || !Number.isFinite(defaultTtlMs) || defaultTtlMs <= 0 || defaultTtlMs > MAX_MODEL_EXCLUSION_TTL_MS)) {
|
|
105
|
+
throw new Error(`config.modelExclusions.defaultTtlMs must be a finite positive number no greater than ${MAX_MODEL_EXCLUSION_TTL_MS}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
65
109
|
function validateOrcaProgressTabsConfig(value: unknown): void {
|
|
66
110
|
if (value === undefined) return;
|
|
67
111
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("config.orcaProgressTabs must be a JSON object");
|
|
@@ -97,6 +141,7 @@ function validateConfig(config: Record<string, unknown>): void {
|
|
|
97
141
|
if (config.defaultSubagentContext !== undefined && config.defaultSubagentContext !== "fresh" && config.defaultSubagentContext !== "fork") {
|
|
98
142
|
throw new Error('config.defaultSubagentContext must be "fresh" or "fork"');
|
|
99
143
|
}
|
|
144
|
+
validateForkContextConfig(config.forkContext);
|
|
100
145
|
if (config.foregroundDetachShortcut !== undefined
|
|
101
146
|
&& (typeof config.foregroundDetachShortcut !== "string" || !isValidKeyId(config.foregroundDetachShortcut))) {
|
|
102
147
|
throw new Error("config.foregroundDetachShortcut must be a valid keybinding string such as \"ctrl+b\"");
|
|
@@ -119,6 +164,8 @@ function validateConfig(config: Record<string, unknown>): void {
|
|
|
119
164
|
validateScheduledRunsConfig(config.scheduledRuns);
|
|
120
165
|
validateFleetKeybindingsConfig(config.fleetKeybindings);
|
|
121
166
|
validateArtifactConfig(config.artifactConfig);
|
|
167
|
+
validateCapacityConfig(config.capacity);
|
|
168
|
+
validateModelExclusionsConfig(config.modelExclusions);
|
|
122
169
|
validateMainWindowRendererConfig(config.mainWindowRenderer);
|
|
123
170
|
validateOrcaProgressTabsConfig(config.orcaProgressTabs);
|
|
124
171
|
}
|
|
@@ -150,6 +197,28 @@ export function updateConfig(updater: (config: ExtensionConfig) => ExtensionConf
|
|
|
150
197
|
return next;
|
|
151
198
|
}
|
|
152
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Resolve the default TTL that the process-wide exclusion store should use.
|
|
202
|
+
*
|
|
203
|
+
* @param config Extension configuration after validation.
|
|
204
|
+
* @returns The configured TTL in milliseconds, or the built-in 24-hour default.
|
|
205
|
+
*/
|
|
206
|
+
// TEST:test/unit/pi-coding-agent-dir.test.ts[loads and applies model exclusion TTL config]
|
|
207
|
+
export function resolveModelExclusionTTL(config: Pick<ExtensionConfig, "modelExclusions">): number {
|
|
208
|
+
return config.modelExclusions?.defaultTtlMs ?? DEFAULT_MODEL_EXCLUSION_TTL_MS;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Apply the configured exclusion policy to the process-wide model store.
|
|
213
|
+
*
|
|
214
|
+
* @param config Extension configuration after validation.
|
|
215
|
+
* @returns Nothing.
|
|
216
|
+
*/
|
|
217
|
+
// TEST:test/unit/pi-coding-agent-dir.test.ts[loads and applies model exclusion TTL config]
|
|
218
|
+
export function applyModelExclusionsConfig(config: Pick<ExtensionConfig, "modelExclusions">): void {
|
|
219
|
+
setDefaultTTL(resolveModelExclusionTTL(config), { shortenExisting: config.modelExclusions?.defaultTtlMs !== undefined });
|
|
220
|
+
}
|
|
221
|
+
|
|
153
222
|
export function resolveAsyncByDefault(config: Pick<ExtensionConfig, "asyncByDefault">): boolean {
|
|
154
223
|
return config.asyncByDefault !== false;
|
|
155
224
|
}
|
|
@@ -159,6 +228,7 @@ export function loadConfig(): ExtensionConfig {
|
|
|
159
228
|
try {
|
|
160
229
|
return readConfigForUpdate(configPath);
|
|
161
230
|
} catch (error) {
|
|
231
|
+
if (error instanceof PrunedForkConfigError) throw error;
|
|
162
232
|
console.error(`Failed to load subagent config from '${configPath}':`, error);
|
|
163
233
|
}
|
|
164
234
|
return {};
|
package/src/extension/doctor.ts
CHANGED
|
@@ -3,7 +3,7 @@ import * as path from "node:path";
|
|
|
3
3
|
import { discoverAgentsAll, type AgentSource } from "../agents/agents.ts";
|
|
4
4
|
import { isAsyncAvailable } from "../runs/background/async-execution.ts";
|
|
5
5
|
import { formatSpawnBudgetSummary, getSpawnBudgetSnapshot } from "../runs/shared/spawn-budget.ts";
|
|
6
|
-
import { getActiveAsyncCapacitySnapshot, resolveMaxActiveAsyncRunsPerSession } from "../runs/background/active-async-capacity.ts";
|
|
6
|
+
import { getActiveAsyncCapacitySnapshot, resolveAbandonedSlotReleaseAfterMs, resolveMaxActiveAsyncRunsPerSession } from "../runs/background/active-async-capacity.ts";
|
|
7
7
|
import { decodeRunFanoutBudgetDescriptor, formatRunFanoutBudget, getRunFanoutBudgetSnapshot, RUN_FANOUT_BUDGET_ENV } from "../runs/shared/run-fanout-budget.ts";
|
|
8
8
|
import { diagnoseIntercomBridge, type IntercomBridgeDiagnostic } from "../intercom/intercom-bridge.ts";
|
|
9
9
|
import { discoverAvailableSkills, type SkillSource } from "../agents/skills.ts";
|
|
@@ -197,13 +197,13 @@ function formatActiveAsyncCapacitySection(input: DoctorReportInput): string[] {
|
|
|
197
197
|
const limit = resolveMaxActiveAsyncRunsPerSession(input.config.maxActiveAsyncRunsPerSession);
|
|
198
198
|
const sessionId = input.currentSessionId ?? input.state.currentSessionId;
|
|
199
199
|
const snapshot = sessionId
|
|
200
|
-
? getActiveAsyncCapacitySnapshot(sessionId, limit, { liveWorkflowRunIds: new Set(input.state.workflowControllers?.keys() ?? []) })
|
|
200
|
+
? getActiveAsyncCapacitySnapshot(sessionId, limit, { liveWorkflowRunIds: new Set(input.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(input.config.capacity?.abandonedSlotReleaseAfterMs) })
|
|
201
201
|
: { used: 0, limit: limit ?? 0 };
|
|
202
202
|
input.state.activeAsyncCapacity = snapshot;
|
|
203
203
|
return [
|
|
204
204
|
`- usage: ${snapshot.used}/${snapshot.limit || "unlimited"} used`,
|
|
205
205
|
"- scope: top-level async runs in the current parent session; foreground and nested workflow children are not charged again",
|
|
206
|
-
"- release: terminal
|
|
206
|
+
"- release: terminal state plus matching observed process-terminal proof, or abandoned-timeout for failed runs with a dead runner PID and stale activity when enabled; false keeps unknown-proof slots",
|
|
207
207
|
];
|
|
208
208
|
}
|
|
209
209
|
|
package/src/extension/index.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* Toggle: async parameter (default: true; set asyncByDefault:false in config.json to opt out)
|
|
10
10
|
*
|
|
11
11
|
* Config file: ~/.pi/agent/extensions/subagent/config.json
|
|
12
|
-
* { "asyncByDefault": true, "defaultSubagentContext": "fork", "forceTopLevelAsync": true, "maxSubagentDepth": 1, "intercomBridge": { "mode": "always", "instructionFile": "./intercom-bridge.md" }, "worktreeSetupHook": "./scripts/setup-worktree.mjs" }
|
|
12
|
+
* { "asyncByDefault": true, "defaultSubagentContext": "fork", "forkContext": { "mode": "pruned", "model": "provider/model" }, "forceTopLevelAsync": true, "maxSubagentDepth": 1, "intercomBridge": { "mode": "always", "instructionFile": "./intercom-bridge.md" }, "worktreeSetupHook": "./scripts/setup-worktree.mjs" }
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { randomUUID } from "node:crypto";
|
|
@@ -24,6 +24,7 @@ import { clearRuntimeAgentsForPi, listRuntimeAgentConfigs, mergeRuntimeAgents }
|
|
|
24
24
|
import { ensureAccessibleDir } from "../shared/accessible-dir.ts";
|
|
25
25
|
import { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from "../shared/artifacts.ts";
|
|
26
26
|
import { resolveCurrentSessionId } from "../shared/session-identity.ts";
|
|
27
|
+
import { getAgentDir } from "../shared/utils.ts";
|
|
27
28
|
import { currentCompletionOwnerId } from "../shared/completion-owner.ts";
|
|
28
29
|
import { cleanupOldChainDirs } from "../shared/settings.ts";
|
|
29
30
|
import { clearLegacyResultAnimationTimer, renderSubagentResult, renderSubagentSummary } from "../tui/render.ts";
|
|
@@ -32,7 +33,7 @@ import { SubagentFleetStatus, resolveFleetViewPlacement } from "../tui/fleet-sta
|
|
|
32
33
|
import { createSubagentParamsSchema } from "./schemas.ts";
|
|
33
34
|
import { createSubagentExecutor, type SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
34
35
|
import { createAsyncJobTracker } from "../runs/background/async-job-tracker.ts";
|
|
35
|
-
import { getActiveAsyncCapacitySnapshot, resolveMaxActiveAsyncRunsPerSession } from "../runs/background/active-async-capacity.ts";
|
|
36
|
+
import { getActiveAsyncCapacitySnapshot, resolveAbandonedSlotReleaseAfterMs, resolveMaxActiveAsyncRunsPerSession } from "../runs/background/active-async-capacity.ts";
|
|
36
37
|
import { cleanupResultIndexes, missionObserverResultCandidateFiles } from "../runs/background/result-files.ts";
|
|
37
38
|
import { ASYNC_RETENTION_DELAY_MS, cleanupAsyncRetention } from "../runs/background/async-retention.ts";
|
|
38
39
|
import { createResultWatcher } from "../runs/background/result-watcher.ts";
|
|
@@ -56,7 +57,7 @@ import { formatSteeringNotice, handleSubagentSteeringNotice, SUBAGENT_STEERING_M
|
|
|
56
57
|
import { SUBAGENT_CHILD_ENV, SUBAGENT_PARENT_SESSION_ENV } from "../runs/shared/pi-args.ts";
|
|
57
58
|
import { resolveCurrentSubagentCapabilityCeiling } from "../runs/shared/capability-ceiling.ts";
|
|
58
59
|
import { formatDuration, shortenPath } from "../shared/formatters.ts";
|
|
59
|
-
import { loadConfig, resolveAsyncByDefault, resolveScheduledStoreRoot } from "./config.ts";
|
|
60
|
+
import { applyModelExclusionsConfig, loadConfig, resolveAsyncByDefault, resolveScheduledStoreRoot } from "./config.ts";
|
|
60
61
|
import { buildSubagentToolDescription, buildSubagentToolPromptMetadata } from "./tool-description.ts";
|
|
61
62
|
import { finalizeToolResult } from "./tool-result.ts";
|
|
62
63
|
import { collectGoalContinuationNotices } from "../missions/goal-driver.ts";
|
|
@@ -382,9 +383,13 @@ export function projectActiveHerdrRuns(state: SubagentState): HerdrStatusRun[] {
|
|
|
382
383
|
.filter((job) => active(job.status))
|
|
383
384
|
.map((job) => {
|
|
384
385
|
const children = job.mode === "workflow" ? foregroundChildrenByWorkflow.get(job.asyncId) : undefined;
|
|
386
|
+
const currentStep = job.steps?.find((step) => step.status === "running")
|
|
387
|
+
?? (job.currentStep !== undefined ? job.steps?.[job.currentStep] : undefined)
|
|
388
|
+
?? job.steps?.find((step) => step.status === "pending");
|
|
385
389
|
return {
|
|
386
390
|
id: job.asyncId,
|
|
387
391
|
agents: children?.length ? children.map((child) => child.agent) : job.agents,
|
|
392
|
+
...(currentStep?.label ? { taskLabel: currentStep.label } : {}),
|
|
388
393
|
needsAttention: job.activityState === "needs_attention" || children?.some((child) => child.needsAttention),
|
|
389
394
|
};
|
|
390
395
|
});
|
|
@@ -401,6 +406,8 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
401
406
|
cleanupOldChainDirs();
|
|
402
407
|
|
|
403
408
|
const config = loadConfig();
|
|
409
|
+
// Apply the process-wide exclusion TTL before any child launch can record a model failure.
|
|
410
|
+
applyModelExclusionsConfig(config);
|
|
404
411
|
const waitToolConfig = resolveWaitToolConfig(config.waitTool);
|
|
405
412
|
const asyncByDefault = resolveAsyncByDefault(config);
|
|
406
413
|
const fleetViewEnabled = config.fleetView !== false;
|
|
@@ -695,6 +702,12 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
695
702
|
0,
|
|
696
703
|
0,
|
|
697
704
|
);
|
|
705
|
+
if (args.workflowScriptPath)
|
|
706
|
+
return new Text(
|
|
707
|
+
`${title}${gap}${theme.fg("accent", args.workflowScriptPath)}${args.async === true ? `${gap}${theme.fg("warning", "[async]")}` : ""}`,
|
|
708
|
+
0,
|
|
709
|
+
0,
|
|
710
|
+
);
|
|
698
711
|
const asyncLabel = args.async === true ? `${gap}${theme.fg("warning", "[async]")}` : "";
|
|
699
712
|
return new Text(
|
|
700
713
|
`${title}${gap}${theme.fg("accent", args.agent || "?")}${asyncLabel}`,
|
|
@@ -836,7 +849,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
836
849
|
state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(
|
|
837
850
|
state.currentSessionId,
|
|
838
851
|
resolveMaxActiveAsyncRunsPerSession(config.maxActiveAsyncRunsPerSession),
|
|
839
|
-
{ liveWorkflowRunIds: new Set(state.workflowControllers?.keys() ?? []) },
|
|
852
|
+
{ liveWorkflowRunIds: new Set(state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(config.capacity?.abandonedSlotReleaseAfterMs) },
|
|
840
853
|
);
|
|
841
854
|
};
|
|
842
855
|
|
|
@@ -846,6 +859,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
846
859
|
goalTurnId = 0;
|
|
847
860
|
state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
848
861
|
state.parentSessionFile = ctx.sessionManager.getSessionFile();
|
|
862
|
+
state.trustedSessionFileRoot = state.parentSessionFile ? path.join(getAgentDir(), "sessions") : undefined;
|
|
849
863
|
state.trustedSessionRoots = [...new Set([
|
|
850
864
|
...(config.defaultSessionDir ? [path.resolve(expandTilde(config.defaultSessionDir))] : []),
|
|
851
865
|
...(state.parentSessionFile ? [getSubagentSessionRoot(state.parentSessionFile)] : []),
|
|
@@ -11,6 +11,7 @@ export interface PublicSubagentExecutionParams {
|
|
|
11
11
|
chainName?: unknown;
|
|
12
12
|
config?: unknown;
|
|
13
13
|
workflowScript?: unknown;
|
|
14
|
+
workflowScriptPath?: unknown;
|
|
14
15
|
isolation?: unknown;
|
|
15
16
|
worktree?: unknown;
|
|
16
17
|
async?: unknown;
|
|
@@ -38,22 +39,28 @@ export type PublicSubagentExecutionNormalization<T> =
|
|
|
38
39
|
* Internal runs.run children and structured owned delegation bypass this boundary.
|
|
39
40
|
*/
|
|
40
41
|
export function normalizePublicSubagentExecution<T extends PublicSubagentExecutionParams>(params: T, options: { asyncByDefault?: boolean } = {}): PublicSubagentExecutionNormalization<T> {
|
|
42
|
+
if (params.workflowScript !== undefined && params.workflowScriptPath !== undefined) {
|
|
43
|
+
return { ok: false, error: "workflowScript and workflowScriptPath are mutually exclusive.", mode: "workflow" };
|
|
44
|
+
}
|
|
45
|
+
const hasWorkflowInput = params.workflowScript !== undefined || params.workflowScriptPath !== undefined;
|
|
46
|
+
const hasValidWorkflowInput = (typeof params.workflowScript === "string" && Boolean(params.workflowScript.trim()))
|
|
47
|
+
|| (typeof params.workflowScriptPath === "string" && Boolean(params.workflowScriptPath.trim()));
|
|
41
48
|
if (params.isolation !== undefined) {
|
|
42
49
|
if (params.isolation !== "none" && params.isolation !== "worktree") {
|
|
43
|
-
return { ok: false, error: "isolation must be 'none' or 'worktree'.", mode:
|
|
50
|
+
return { ok: false, error: "isolation must be 'none' or 'worktree'.", mode: hasWorkflowInput ? "workflow" : "management" };
|
|
44
51
|
}
|
|
45
52
|
const isolationWorktree = params.isolation === "worktree";
|
|
46
53
|
if (params.worktree !== undefined && params.worktree !== isolationWorktree) {
|
|
47
|
-
return { ok: false, error: `isolation '${params.isolation}' conflicts with worktree: ${String(params.worktree)}.`, mode:
|
|
54
|
+
return { ok: false, error: `isolation '${params.isolation}' conflicts with worktree: ${String(params.worktree)}.`, mode: hasWorkflowInput ? "workflow" : "management" };
|
|
48
55
|
}
|
|
49
56
|
const { isolation: _isolation, ...normalizedParams } = params;
|
|
50
57
|
params = { ...normalizedParams, worktree: isolationWorktree } as T;
|
|
51
58
|
}
|
|
52
59
|
if (params.runFanoutBudget !== undefined || params.runFanoutAdmitted !== undefined) {
|
|
53
|
-
return { ok: false, error: "Public execution does not accept internal run fan-out fields.", mode:
|
|
60
|
+
return { ok: false, error: "Public execution does not accept internal run fan-out fields.", mode: hasWorkflowInput ? "workflow" : "management" };
|
|
54
61
|
}
|
|
55
62
|
if (params.workflowParentRunId !== undefined || params.workflowKey !== undefined || params.workflowChildAsyncId !== undefined || params.workflowAwaitAsync !== undefined || params.workflowParentDeadlineAt !== undefined || params.suppressRoutineResultIntercom !== undefined) {
|
|
56
|
-
return { ok: false, error: "Public execution does not accept internal workflow child fields.", mode:
|
|
63
|
+
return { ok: false, error: "Public execution does not accept internal workflow child fields.", mode: hasWorkflowInput ? "workflow" : "management" };
|
|
57
64
|
}
|
|
58
65
|
const action = params.action;
|
|
59
66
|
if (action !== undefined && (typeof action !== "string" || !action.trim())) {
|
|
@@ -90,17 +97,26 @@ export function normalizePublicSubagentExecution<T extends PublicSubagentExecuti
|
|
|
90
97
|
if (legacyAction === "parallel" || legacyAction === "tasks" || legacyAction === "chain") {
|
|
91
98
|
return { ok: false, error: "Legacy top-level chain and parallel inputs were removed; use workflowScript.", mode: "workflow" };
|
|
92
99
|
}
|
|
100
|
+
if (normalizedAction === "validate") {
|
|
101
|
+
if (params.agent !== undefined || params.task !== undefined || params.step !== undefined) {
|
|
102
|
+
return { ok: false, error: "validate requires workflowScript or workflowScriptPath and does not accept direct agent, task, or step execution fields.", mode: "management" };
|
|
103
|
+
}
|
|
104
|
+
if (!hasValidWorkflowInput) {
|
|
105
|
+
return { ok: false, error: "validate requires a non-empty workflowScript or workflowScriptPath.", mode: "management" };
|
|
106
|
+
}
|
|
107
|
+
return { ok: true, params: { ...params, action: normalizedAction } };
|
|
108
|
+
}
|
|
93
109
|
if (normalizedAction === "schedule.create") {
|
|
94
110
|
if (params.agent !== undefined || params.task !== undefined || params.step !== undefined) {
|
|
95
|
-
return { ok: false, error: "schedule.create requires workflowScript and does not accept direct agent, task, or step execution fields.", mode: "management" };
|
|
111
|
+
return { ok: false, error: "schedule.create requires workflowScript or workflowScriptPath and does not accept direct agent, task, or step execution fields.", mode: "management" };
|
|
96
112
|
}
|
|
97
|
-
if (
|
|
98
|
-
return { ok: false, error: "schedule.create requires a non-empty workflowScript.", mode: "management" };
|
|
113
|
+
if (!hasValidWorkflowInput) {
|
|
114
|
+
return { ok: false, error: "schedule.create requires a non-empty workflowScript or workflowScriptPath.", mode: "management" };
|
|
99
115
|
}
|
|
100
116
|
return { ok: true, params: { ...params, action: normalizedAction } };
|
|
101
117
|
}
|
|
102
|
-
if (
|
|
103
|
-
return { ok: false, error: "
|
|
118
|
+
if (hasWorkflowInput) {
|
|
119
|
+
return { ok: false, error: "Workflow execution must omit action; only validate and schedule.create accept action with workflowScript or workflowScriptPath.", mode: "management" };
|
|
104
120
|
}
|
|
105
121
|
if (params.task !== undefined) {
|
|
106
122
|
return { ok: false, error: "Structured single-child task cannot be combined with a management/control action.", mode: "management" };
|
|
@@ -110,8 +126,8 @@ export function normalizePublicSubagentExecution<T extends PublicSubagentExecuti
|
|
|
110
126
|
if (params.step !== undefined) {
|
|
111
127
|
return { ok: false, error: "step is not a public execution field; use workflowScript for orchestration.", mode: "workflow" };
|
|
112
128
|
}
|
|
113
|
-
if (
|
|
114
|
-
return { ok: false, error: "Structured single-child execution cannot be combined with workflowScript.", mode: "workflow" };
|
|
129
|
+
if (hasWorkflowInput && (params.agent !== undefined || params.task !== undefined)) {
|
|
130
|
+
return { ok: false, error: "Structured single-child execution cannot be combined with workflowScript or workflowScriptPath.", mode: "workflow" };
|
|
115
131
|
}
|
|
116
132
|
if (params.agent !== undefined || params.task !== undefined) {
|
|
117
133
|
if (typeof params.agent !== "string" || !params.agent.trim()) {
|
|
@@ -129,8 +145,8 @@ export function normalizePublicSubagentExecution<T extends PublicSubagentExecuti
|
|
|
129
145
|
} as T,
|
|
130
146
|
};
|
|
131
147
|
}
|
|
132
|
-
if (
|
|
133
|
-
return { ok: false, error: "Execution requires either { agent, task? } for one child or a non-empty workflowScript for orchestration.", mode: "workflow" };
|
|
148
|
+
if (!hasValidWorkflowInput) {
|
|
149
|
+
return { ok: false, error: "Execution requires either { agent, task? } for one child or a non-empty workflowScript or workflowScriptPath for orchestration.", mode: "workflow" };
|
|
134
150
|
}
|
|
135
151
|
return { ok: true, params };
|
|
136
152
|
}
|
package/src/extension/rpc.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type AsyncJobStep,
|
|
12
12
|
type Details,
|
|
13
13
|
type SubagentState,
|
|
14
|
+
type TokenUsage,
|
|
14
15
|
DIRS,
|
|
15
16
|
SUBAGENT_ASYNC_COMPLETE_EVENT,
|
|
16
17
|
SUBAGENT_CHILD_STATUS_EVENT,
|
|
@@ -97,7 +98,7 @@ export interface SubagentRpcFleetEntry {
|
|
|
97
98
|
model?: string;
|
|
98
99
|
effort?: string;
|
|
99
100
|
startedAt: number;
|
|
100
|
-
tokens:
|
|
101
|
+
tokens: TokenUsage;
|
|
101
102
|
goal?: string;
|
|
102
103
|
}
|
|
103
104
|
|
|
@@ -122,7 +123,7 @@ function displayText(value: unknown, maxLength: number): string | undefined {
|
|
|
122
123
|
return normalized ? truncateDisplayText(normalized, maxLength) : undefined;
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
function publicTokens(value: unknown):
|
|
126
|
+
function publicTokens(value: unknown): TokenUsage {
|
|
126
127
|
const record = isRecord(value) ? value : {};
|
|
127
128
|
const count = (field: "input" | "output" | "total") => {
|
|
128
129
|
const raw = record[field];
|
|
@@ -133,7 +134,21 @@ function publicTokens(value: unknown): { input: number; output: number; total: n
|
|
|
133
134
|
const input = count("input");
|
|
134
135
|
const output = count("output");
|
|
135
136
|
const sum = Math.min(Number.MAX_SAFE_INTEGER, input + output);
|
|
136
|
-
|
|
137
|
+
const optionalCount = (field: "window" | "windowPeak") => {
|
|
138
|
+
const raw = record[field];
|
|
139
|
+
return typeof raw === "number" && Number.isFinite(raw) && raw >= 0
|
|
140
|
+
? Math.min(Number.MAX_SAFE_INTEGER, Math.floor(raw))
|
|
141
|
+
: undefined;
|
|
142
|
+
};
|
|
143
|
+
const window = optionalCount("window");
|
|
144
|
+
const windowPeak = optionalCount("windowPeak");
|
|
145
|
+
return {
|
|
146
|
+
input,
|
|
147
|
+
output,
|
|
148
|
+
total: Math.max(sum, count("total")),
|
|
149
|
+
...(window !== undefined ? { window } : {}),
|
|
150
|
+
...(windowPeak !== undefined ? { windowPeak } : {}),
|
|
151
|
+
};
|
|
137
152
|
}
|
|
138
153
|
|
|
139
154
|
function activeState(value: unknown): boolean {
|
|
@@ -188,7 +203,7 @@ function buildFleetStatus(
|
|
|
188
203
|
model: child.model,
|
|
189
204
|
effort: child.thinking,
|
|
190
205
|
startedAt: child.startedAt,
|
|
191
|
-
tokens: { input: child.inputTokens ?? 0, output: child.outputTokens ?? 0, total: child.tokens ?? 0 },
|
|
206
|
+
tokens: { input: child.inputTokens ?? 0, output: child.outputTokens ?? 0, total: child.tokens ?? 0, ...(child.window !== undefined ? { window: child.window } : {}), ...(child.windowPeak !== undefined ? { windowPeak: child.windowPeak } : {}) },
|
|
192
207
|
});
|
|
193
208
|
} else {
|
|
194
209
|
addCandidate({
|
|
@@ -197,7 +212,7 @@ function buildFleetStatus(
|
|
|
197
212
|
model: control.model,
|
|
198
213
|
effort: control.thinking,
|
|
199
214
|
startedAt: control.startedAt,
|
|
200
|
-
tokens: { input: control.inputTokens ?? 0, output: control.outputTokens ?? 0, total: control.tokens ?? 0 },
|
|
215
|
+
tokens: { input: control.inputTokens ?? 0, output: control.outputTokens ?? 0, total: control.tokens ?? 0, ...(control.window !== undefined ? { window: control.window } : {}), ...(control.windowPeak !== undefined ? { windowPeak: control.windowPeak } : {}) },
|
|
201
216
|
});
|
|
202
217
|
}
|
|
203
218
|
}
|
package/src/extension/schemas.ts
CHANGED
|
@@ -145,6 +145,7 @@ export const ParallelTaskSchema = Type.Object({
|
|
|
145
145
|
progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking in {chain_dir}" })),
|
|
146
146
|
skill: Type.Optional(SkillOverride),
|
|
147
147
|
model: Type.Optional(Type.String({ description: "Override model for this task" })),
|
|
148
|
+
fast: Type.Optional(Type.Boolean({ description: "Opt into priority service tier for supported native OpenAI-Codex child models. This can increase quota or cost." })),
|
|
148
149
|
toolBudget: Type.Optional(ToolBudgetOverride),
|
|
149
150
|
acceptance: Type.Optional(AcceptanceOverride),
|
|
150
151
|
agentContract: Type.Optional(AgentContractOverride),
|
|
@@ -175,6 +176,7 @@ export const DynamicParallelTemplateSchema = Type.Object({
|
|
|
175
176
|
progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking in {chain_dir}" })),
|
|
176
177
|
skill: Type.Optional(SkillOverride),
|
|
177
178
|
model: Type.Optional(Type.String({ description: "Override model for this task" })),
|
|
179
|
+
fast: Type.Optional(Type.Boolean({ description: "Opt into priority service tier for supported native OpenAI-Codex child models. This can increase quota or cost." })),
|
|
178
180
|
toolBudget: Type.Optional(ToolBudgetOverride),
|
|
179
181
|
acceptance: Type.Optional(AcceptanceOverride),
|
|
180
182
|
agentContract: Type.Optional(AgentContractOverride),
|
|
@@ -203,6 +205,7 @@ export const ChainItem = Type.Object({
|
|
|
203
205
|
progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking in {chain_dir}" })),
|
|
204
206
|
skill: Type.Optional(SkillOverride),
|
|
205
207
|
model: Type.Optional(Type.String({ description: "Override model for this step" })),
|
|
208
|
+
fast: Type.Optional(Type.Boolean({ description: "Opt into priority service tier for supported native OpenAI-Codex child models. This can increase quota or cost." })),
|
|
206
209
|
toolBudget: Type.Optional(ToolBudgetOverride),
|
|
207
210
|
acceptance: Type.Optional(AcceptanceOverride),
|
|
208
211
|
agentContract: Type.Optional(AgentContractOverride),
|
|
@@ -254,10 +257,11 @@ const ControlOverrides = Type.Object({
|
|
|
254
257
|
|
|
255
258
|
const SubagentParamProperties = {
|
|
256
259
|
agent: Type.Optional(Type.String({ description: "Agent for one-child execution, or target for agent management actions." })),
|
|
257
|
-
task: Type.Optional(Type.String({ description: "Optional one-child task. Requires agent; cannot combine with action or
|
|
260
|
+
task: Type.Optional(Type.String({ description: "Optional one-child task. Requires agent; cannot combine with action, workflowScript, or workflowScriptPath." })),
|
|
261
|
+
extensionBindings: Type.Optional(Type.Unsafe({ type: "object", maxProperties: 16, additionalProperties: true, description: "Namespaced, bounded plain-JSON metadata delivered only to the child runtime. Namespace keys use package.name/1 syntax." })),
|
|
258
262
|
// Management action (when present, tool operates in management mode)
|
|
259
263
|
action: Type.Optional(Type.String({ minLength: 1,
|
|
260
|
-
description: "Optional management/control action. Omit this field for structured single-child or
|
|
264
|
+
description: "Optional management/control action. Use action='validate' with workflowScript or workflowScriptPath for offline checks. Omit this field for structured single-child or workflow execution; otherwise, use it only for management/control actions."
|
|
261
265
|
})),
|
|
262
266
|
name: Type.Optional(Type.String({ description: "Human-readable name for action='schedule.create'." })),
|
|
263
267
|
id: Type.Optional(Type.String({
|
|
@@ -309,12 +313,13 @@ const SubagentParamProperties = {
|
|
|
309
313
|
description: "Agent config for create/update. Object or JSON string."
|
|
310
314
|
})),
|
|
311
315
|
workflowScript: Type.Optional(Type.String({ minLength: 1, description: "Trusted inline JavaScript statement body. 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. Use runs.all([...]), await runs.steer(key, message, {mode?, index?, ackTimeoutMs?}), runs.status(id), runs.ref(s), emit(value), console, and return. 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." })),
|
|
316
|
+
workflowScriptPath: Type.Optional(Type.String({ minLength: 1, description: "Path to a trusted JavaScript workflow file. Mutually exclusive with workflowScript. Relative paths resolve against the request cwd. The host reads the file before the filesystem-free workflow sandbox starts." })),
|
|
312
317
|
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." })),
|
|
313
318
|
isolation: Type.Optional(Type.String({ enum: ["none", "worktree"], description: "Workflow child isolation. none runs in the shared cwd; worktree requires managed git worktree isolation." })),
|
|
314
319
|
worktree: Type.Optional(Type.Boolean({ description: "Managed child isolation. true gives each workflow child a separate git worktree; an individual runs.run/runs.all item can override a workflow default with worktree:false." })),
|
|
315
320
|
context: Type.Optional(Type.String({
|
|
316
321
|
enum: ["fresh", "fork", "profile"],
|
|
317
|
-
description: "'fresh' or 'fork' to branch from parent session, or 'profile' to require the selected agent's declared defaultContext. Explicit fresh/fork overrides every child; profile ignores config defaultSubagentContext and fails when an agent has no defaultContext. If omitted, config defaultSubagentContext wins over each agent defaultContext; implicit fork needs a persisted parent session and leaf, else fresh.",
|
|
322
|
+
description: "'fresh' or 'fork' to branch from parent session, or 'profile' to require the selected agent's declared defaultContext. Explicit fresh/fork overrides every child; profile ignores config defaultSubagentContext and fails when an agent has no defaultContext. If omitted, config defaultSubagentContext wins over each agent defaultContext; implicit fork needs a persisted parent session and leaf, else fresh. Config forkContext may prune resolved forks before spawn without adding another context value.",
|
|
318
323
|
})),
|
|
319
324
|
async: Type.Optional(Type.Boolean({ description: "Run in background unless asyncByDefault:false. Set false only when the parent must block until completion." })),
|
|
320
325
|
timeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "Timeout. Foreground and single async runs use config timeoutMs, else 30m; async composites have no default parent deadline. Alias maxRuntimeMs." })),
|
|
@@ -343,6 +348,7 @@ const SubagentParamProperties = {
|
|
|
343
348
|
outputMode: Type.Optional(OutputModeOverride),
|
|
344
349
|
skill: Type.Optional(SkillOverride),
|
|
345
350
|
model: Type.Optional(Type.String({ description: "Default child model override. Full provider/id values are accepted; bare ids resolve from the active registry." })),
|
|
351
|
+
fast: Type.Optional(Type.Boolean({ description: "Opt into priority service tier for supported native OpenAI-Codex child models. Default false. This can increase quota or cost." })),
|
|
346
352
|
outputSchema: Type.Optional(JsonSchemaObject),
|
|
347
353
|
agentContract: Type.Optional(AgentContractOverride),
|
|
348
354
|
acceptance: Type.Optional(AcceptanceOverride),
|