pi-subagents 0.65.1 → 0.67.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 +123 -0
- package/README.md +5 -4
- package/agents/evidence-auditor.md +34 -0
- package/agents/researcher.md +23 -13
- package/agents/reviewer.md +3 -2
- package/docs/agents.md +20 -3
- package/docs/configuration.md +25 -5
- package/docs/extension-api.md +124 -18
- package/docs/missions.md +8 -0
- package/docs/models.md +59 -2
- package/docs/observability.md +46 -6
- package/docs/standalone-background.md +49 -0
- package/docs/tool-reference.md +20 -10
- package/docs/watchdog.md +35 -4
- package/docs/workflows.md +40 -19
- package/inspector-runner.mjs +2 -2
- package/package.json +2 -1
- package/prompts/parallel-review.md +1 -1
- package/{runner-server-preload.mjs → runner-peer-preload.mjs} +8 -3
- package/skills/pi-subagents/SKILL.md +14 -0
- package/skills/pi-subagents/references/execution-controls.md +20 -5
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +2 -2
- package/src/agents/advertised-agent-prompt.ts +94 -0
- package/src/agents/agent-management.ts +14 -1
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +14 -0
- package/src/agents/builtin-names.ts +1 -0
- package/src/api/delegation.ts +4 -0
- package/src/api/preflight.ts +76 -45
- package/src/api/shared-types.ts +3 -1
- package/src/api/workflow-resources.ts +6 -0
- package/src/extension/fanout-child.ts +63 -4
- package/src/extension/index.ts +58 -8
- package/src/extension/public-execution.ts +4 -3
- package/src/extension/rpc.ts +8 -21
- package/src/extension/schemas.ts +71 -80
- package/src/extension/tool-description.ts +29 -81
- package/src/inspectors/actions.ts +148 -0
- package/src/inspectors/ghostty/actions.ts +74 -0
- package/src/inspectors/ghostty/plugin.ts +17 -0
- package/src/inspectors/herdr/actions.ts +99 -179
- package/src/inspectors/herdr/plugin.ts +20 -0
- package/src/inspectors/herdr/project-panes.ts +1 -1
- package/src/inspectors/{herdr/inspector-runner.ts → inspector-runner.ts} +12 -12
- package/src/inspectors/plugins.ts +8 -0
- package/src/inspectors/{herdr/session-roots-codec.ts → session-roots-codec.ts} +3 -14
- package/src/inspectors/types.ts +51 -0
- package/src/intercom/intercom-bridge.ts +50 -8
- package/src/intercom/native-supervisor-channel.ts +104 -67
- package/src/runs/background/active-async-capacity.ts +22 -18
- package/src/runs/background/async-execution.ts +45 -56
- package/src/runs/background/async-job-tracker.ts +35 -3
- package/src/runs/background/async-resume.ts +5 -9
- package/src/runs/background/async-status-snapshot.ts +10 -12
- package/src/runs/background/async-status.ts +17 -9
- package/src/runs/background/auto-drain.ts +44 -30
- package/src/runs/background/binary-bootstrap.ts +33 -0
- package/src/runs/background/chain-root-attachment.ts +8 -0
- package/src/runs/background/control-channel.ts +78 -44
- package/src/runs/background/fleet-view.ts +30 -2
- package/src/runs/background/notify.ts +117 -13
- package/src/runs/background/owned-process-tree.ts +35 -8
- package/src/runs/background/process-terminal.ts +23 -23
- package/src/runs/background/run-child-session.ts +121 -36
- package/src/runs/background/run-status.ts +78 -5
- package/src/runs/background/runner-aliases.ts +28 -9
- package/src/runs/background/runner-child-launch.ts +88 -0
- package/src/runs/background/runner-child-sessions.ts +5 -4
- package/src/runs/background/scheduled-runs.ts +40 -13
- package/src/runs/background/stale-run-reconciler.ts +3 -1
- package/src/runs/background/steering.ts +20 -2
- package/src/runs/background/subagent-runner.ts +458 -239
- package/src/runs/background/subagent-wait.ts +54 -8
- package/src/runs/background/wait-completions.ts +4 -0
- package/src/runs/background/wait-tool.ts +1 -1
- package/src/runs/foreground/async-steering-action.ts +37 -7
- package/src/runs/foreground/execution.ts +145 -56
- package/src/runs/foreground/prompt-audit.ts +3 -1
- package/src/runs/foreground/subagent-executor.ts +584 -297
- package/src/runs/foreground/workflow-detach-reconcile.ts +10 -5
- package/src/runs/foreground/workflow-foreground-steering.ts +57 -2
- package/src/runs/shared/acceptance.ts +7 -4
- package/src/runs/shared/agent-contract.ts +1 -1
- package/src/runs/shared/async-status-projection.ts +51 -47
- package/src/runs/shared/capability-ceiling.ts +2 -0
- package/src/runs/shared/child-hooks.ts +167 -3
- package/src/runs/shared/child-launch.ts +28 -13
- package/src/runs/shared/child-lifecycle.ts +6 -3
- package/src/runs/shared/child-runtime-config.ts +3 -1
- package/src/runs/shared/child-session.ts +75 -8
- package/src/runs/shared/child-tool-plan.ts +124 -5
- package/src/runs/shared/completion-evidence.ts +2 -2
- package/src/runs/shared/completion-guard.ts +6 -3
- package/src/runs/shared/effective-system-prompt.ts +33 -0
- package/src/runs/shared/external-cli-runner.ts +9 -7
- package/src/runs/shared/host-step-status.ts +11 -11
- package/src/runs/shared/llm-intent-arbiter.ts +21 -11
- package/src/runs/shared/model-fallback.ts +12 -6
- package/src/runs/shared/nested-events.ts +5 -5
- package/src/runs/shared/orca-progress-tabs.ts +7 -1
- package/src/runs/shared/parallel-handoff.ts +57 -12
- package/src/runs/shared/parallel-utils.ts +2 -2
- package/src/runs/shared/pi-spawn.ts +10 -0
- package/src/runs/shared/readonly-drain-observation.ts +42 -0
- package/src/runs/shared/readonly-model-continuation.ts +69 -0
- package/src/runs/shared/readonly-session-evidence.ts +307 -0
- package/src/runs/shared/run-fanout-budget.ts +8 -8
- package/src/runs/shared/runtime-acknowledged-extensions.ts +3 -3
- package/src/runs/shared/subagent-prompt-runtime.ts +20 -4
- package/src/runs/shared/task-intent.ts +46 -13
- package/src/runs/shared/workflow-async-child-guidance.ts +18 -0
- package/src/runs/shared/worktree-setup-command.ts +190 -0
- package/src/runs/shared/worktree.ts +366 -208
- package/src/shared/fork-context.ts +15 -72
- package/src/shared/launch-contract.ts +65 -2
- package/src/shared/opencode-session-headers.ts +30 -0
- package/src/shared/types.ts +85 -61
- package/src/shared/utils.ts +7 -2
- package/src/shared/workflow-child-permit.ts +18 -13
- package/src/slash/delegation-adapters.ts +3 -1
- package/src/slash/delegation-request.ts +14 -0
- package/src/slash/slash-commands.ts +2 -1
- package/src/slash/subagents-admin.ts +11 -4
- package/src/tui/fleet-status.ts +164 -19
- package/src/tui/fleet.ts +27 -19
- package/src/tui/render.ts +172 -33
- package/src/watchdog/child-status.ts +8 -0
- package/src/watchdog/model-selection.ts +20 -0
- package/src/watchdog/permission-arbiter.ts +3 -1
- package/src/watchdog/register-child.ts +1 -0
- package/src/watchdog/register-main.ts +31 -27
- package/src/watchdog/review.ts +132 -67
- package/src/watchdog/runtime.ts +82 -20
- package/src/watchdog/scope.ts +1 -1
- package/src/watchdog/settings.ts +9 -3
- package/src/watchdog/tool-actions.ts +13 -12
- package/src/watchdog/turn-delta.ts +23 -0
- package/src/watchdog/types.ts +4 -0
- package/src/workflows/chat-progress.ts +3 -3
- package/src/workflows/scripted-workflow.ts +275 -17
- package/src/workflows/workflow-checklist.ts +13 -17
- package/src/workflows/workflow-child-summary.ts +57 -8
- package/src/workflows/workflow-preflight.ts +19 -19
- package/src/workflows/workflow-receipt.ts +3 -3
- package/src/workflows/workflow-resources.ts +96 -21
- package/src/workflows/workflow-settlement.ts +3 -0
- /package/src/inspectors/{herdr/shell-command.ts → shell-command.ts} +0 -0
|
@@ -6,6 +6,7 @@ export const KNOWN_FIELDS = new Set([
|
|
|
6
6
|
"name",
|
|
7
7
|
"package",
|
|
8
8
|
"description",
|
|
9
|
+
"advertise",
|
|
9
10
|
"alias",
|
|
10
11
|
"aliases",
|
|
11
12
|
"tools",
|
|
@@ -62,6 +63,7 @@ export function serializeAgent(config: AgentConfig, options: SerializeAgentOptio
|
|
|
62
63
|
lines.push(`name: ${frontmatterNameForConfig(config)}`);
|
|
63
64
|
if (config.packageName) lines.push(`package: ${config.packageName}`);
|
|
64
65
|
lines.push(`description: ${config.description}`);
|
|
66
|
+
if (config.advertise === true || preserve("advertise")) lines.push(`advertise: ${config.advertise === true ? "true" : "false"}`);
|
|
65
67
|
const aliasesValue = joinComma(config.aliases);
|
|
66
68
|
if (aliasesValue || preserve("alias", "aliases")) lines.push(`aliases: ${aliasesValue ?? ""}`);
|
|
67
69
|
|
package/src/agents/agents.ts
CHANGED
|
@@ -135,6 +135,7 @@ export interface AgentConfig {
|
|
|
135
135
|
packageSourceVersion?: string;
|
|
136
136
|
packageSourceRoot?: string;
|
|
137
137
|
description: string;
|
|
138
|
+
advertise?: boolean;
|
|
138
139
|
aliases?: string[];
|
|
139
140
|
tools?: string[];
|
|
140
141
|
excludeTools?: string[];
|
|
@@ -180,6 +181,12 @@ export interface AgentConfig {
|
|
|
180
181
|
override?: BuiltinAgentOverrideInfo;
|
|
181
182
|
modelSource?: AgentModelSourceInfo;
|
|
182
183
|
maxThinking?: ThinkingLevel;
|
|
184
|
+
/**
|
|
185
|
+
* Digest of the parsed definition, set when a runtime overlay such as the
|
|
186
|
+
* Intercom bridge rewrites launch-affecting fields. Launch identity reads
|
|
187
|
+
* this instead of re-hashing the overlaid copy.
|
|
188
|
+
*/
|
|
189
|
+
definitionDigest?: string;
|
|
183
190
|
}
|
|
184
191
|
|
|
185
192
|
type ProjectRootResolution = "nearest" | "git-root";
|
|
@@ -1981,6 +1988,12 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
|
|
|
1981
1988
|
|
|
1982
1989
|
const runner = parseAgentRunnerFrontmatter(frontmatter.runner, localName);
|
|
1983
1990
|
validateExternalRunnerProfile(frontmatter, localName, runner);
|
|
1991
|
+
let advertise: boolean | undefined;
|
|
1992
|
+
if (frontmatter.advertise !== undefined) {
|
|
1993
|
+
if (frontmatter.advertise === "true") advertise = true;
|
|
1994
|
+
else if (frontmatter.advertise === "false") advertise = false;
|
|
1995
|
+
else throw new Error(`Agent '${localName}' has invalid advertise frontmatter; expected true or false.`);
|
|
1996
|
+
}
|
|
1984
1997
|
const rawTools = parseFrontmatterList(frontmatter.tools);
|
|
1985
1998
|
const parsedTools = splitToolList(rawTools);
|
|
1986
1999
|
const tools = parsedTools.tools ?? [];
|
|
@@ -2105,6 +2118,7 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
|
|
|
2105
2118
|
...(packageSource?.packageVersion ? { packageSourceVersion: packageSource.packageVersion } : {}),
|
|
2106
2119
|
...(packageSource?.packageRoot ? { packageSourceRoot: packageSource.packageRoot } : {}),
|
|
2107
2120
|
description: frontmatter.description,
|
|
2121
|
+
...(advertise !== undefined ? { advertise } : {}),
|
|
2108
2122
|
...(aliases !== undefined ? { aliases } : {}),
|
|
2109
2123
|
...(rawTools !== undefined ? { tools } : {}),
|
|
2110
2124
|
...(excludeTools !== undefined ? { excludeTools } : {}),
|
package/src/api/delegation.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { IntercomBridgeConfig } from "../shared/types.ts";
|
|
2
|
+
|
|
1
3
|
// This is the established extension-to-extension transport. The structured
|
|
2
4
|
// delegation API intentionally reuses it instead of adding a second event
|
|
3
5
|
// protocol. Unstructured legacy direct payloads are rejected.
|
|
@@ -35,6 +37,8 @@ export interface SubagentDelegationRequest {
|
|
|
35
37
|
toolBudget?: SubagentDelegationToolBudget;
|
|
36
38
|
skill?: string | string[] | boolean;
|
|
37
39
|
artifacts?: boolean;
|
|
40
|
+
/** Per-launch bridge config; replaces the global `intercomBridge` config. Pass the same value to preflight to compare digests. */
|
|
41
|
+
intercomBridge?: IntercomBridgeConfig;
|
|
38
42
|
result: SubagentDelegationResultRequest;
|
|
39
43
|
}
|
|
40
44
|
|
package/src/api/preflight.ts
CHANGED
|
@@ -3,30 +3,36 @@ import * as path from "node:path";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
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
|
-
import {
|
|
7
|
-
import { buildAgentMemoryInjection } from "../agents/agent-memory.ts";
|
|
6
|
+
import { normalizeSkillInput, resolveSkillsWithFallback } from "../agents/skills.ts";
|
|
8
7
|
import { buildModelCandidates, inheritsParentModel, resolveEffectiveSubagentModel, resolveModelOrigin, type AvailableModelInfo, type ParentModel } from "../runs/shared/model-fallback.ts";
|
|
9
8
|
import { resolveModelScopesForAgent } from "../runs/shared/model-scope.ts";
|
|
10
9
|
import { applyThinkingSuffix, resolvePiLaunchToolPlan, type PiLaunchToolPlan } from "../runs/shared/child-tool-plan.ts";
|
|
11
|
-
import {
|
|
10
|
+
import { buildEffectiveSystemPrompt } from "../runs/shared/effective-system-prompt.ts";
|
|
11
|
+
import { normalizeSingleOutputOverride, resolveSingleOutputPath } from "../runs/shared/single-output.ts";
|
|
12
12
|
import { getArtifactPaths, getArtifactsDir } from "../shared/artifacts.ts";
|
|
13
13
|
import { resolveEffectiveThinking } from "../shared/model-info.ts";
|
|
14
14
|
import { assertThinkingWithinCeiling, intersectThinkingCeilings, type ThinkingLevel } from "../shared/thinking-ceiling.ts";
|
|
15
|
-
import { SUBAGENT_LIFECYCLE_ARTIFACT_VERSION, type ArtifactDirPreference, type ArtifactPaths, type JsonSchemaObject, type OutputMode } from "../shared/types.ts";
|
|
15
|
+
import { SUBAGENT_LIFECYCLE_ARTIFACT_VERSION, type ArtifactDirPreference, type ArtifactPaths, type IntercomBridgeConfig, type IntercomBridgeMode, type JsonSchemaObject, type OutputMode } from "../shared/types.ts";
|
|
16
16
|
import { capabilityCeilingAgentRestrictionMessage, intersectSubagentCapabilityCeilings, type ResolvedSubagentCapabilityCeiling, type SubagentCapabilityAudit } from "../runs/shared/capability-ceiling.ts";
|
|
17
17
|
import { resolvePermissionRules } from "../runs/shared/permissions.ts";
|
|
18
18
|
import type { ResolvedMcpDirectToolSelection } from "../runs/shared/mcp-direct-tool-allowlist.ts";
|
|
19
19
|
import { resolveStepBehavior } from "../shared/settings.ts";
|
|
20
20
|
import { canPreferForkFromSnapshot, resolveSubagentLaunchContext } from "../shared/fork-context.ts";
|
|
21
21
|
import { loadConfig } from "../extension/config.ts";
|
|
22
|
-
import {
|
|
22
|
+
import { applyIntercomBridgeToAgent, resolveIntercomBridge, validateIntercomBridgeConfig } from "../intercom/intercom-bridge.ts";
|
|
23
|
+
import { AGENT_DEFINITION_PROJECTION_VERSION, resolveLaunchBinding, stableJsonDigest } from "../shared/launch-contract.ts";
|
|
23
24
|
import { DIRS, TEMP_ROOT_DIR } from "../shared/types.ts";
|
|
24
25
|
import { processTerminalCandidatePath, processTerminalPath } from "../runs/background/process-terminal.ts";
|
|
25
26
|
import { resultFilePath } from "../runs/background/result-files.ts";
|
|
26
27
|
import { nestedResultsPath } from "../runs/shared/nested-events.ts";
|
|
27
28
|
import { normalizeExtensionBindings, type ExtensionBindings } from "../runs/shared/extension-bindings.ts";
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
// v3: the contract reports the resolved Intercom bridge state and binds its
|
|
31
|
+
// prompt and tools into launchContractDigest, matching execution (#2127).
|
|
32
|
+
export const SUBAGENT_LAUNCH_CONTRACT_VERSION = 3 as const;
|
|
33
|
+
|
|
34
|
+
/** Stands in for the parent session target when the host does not supply one; only custom templates that name the session read it. */
|
|
35
|
+
const PREFLIGHT_ORCHESTRATOR_TARGET = "preflight";
|
|
30
36
|
|
|
31
37
|
export type SubagentLaunchContractReasonCode =
|
|
32
38
|
| "missing_agent"
|
|
@@ -38,7 +44,8 @@ export type SubagentLaunchContractReasonCode =
|
|
|
38
44
|
| "unsupported_mode"
|
|
39
45
|
| "restricted_agent"
|
|
40
46
|
| "thinking_ceiling"
|
|
41
|
-
| "invalid_extension_bindings"
|
|
47
|
+
| "invalid_extension_bindings"
|
|
48
|
+
| "invalid_intercom_bridge";
|
|
42
49
|
|
|
43
50
|
export type SubagentLaunchContractDiagnosticCode = SubagentLaunchContractReasonCode | "host_required" | "snapshot_warning" | "workspace_scope_authority";
|
|
44
51
|
|
|
@@ -80,8 +87,23 @@ export interface SubagentLaunchContractInput {
|
|
|
80
87
|
nestedRootRunId?: string;
|
|
81
88
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
82
89
|
inheritedCapabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
90
|
+
/** Builtin tool names the host runtime provides; used to intersect agent-declared tools. */
|
|
91
|
+
hostAvailableBuiltins?: readonly string[];
|
|
92
|
+
/** Per-launch bridge config; replaces the global `intercomBridge` config exactly as the tool and delegation overrides do. */
|
|
93
|
+
intercomBridge?: IntercomBridgeConfig;
|
|
94
|
+
/**
|
|
95
|
+
* Supervisor session target the host will hand to the child. Only a custom
|
|
96
|
+
* bridge instruction file that names the session needs it; the default
|
|
97
|
+
* template is session-independent.
|
|
98
|
+
*/
|
|
99
|
+
orchestratorTarget?: string;
|
|
83
100
|
}
|
|
84
101
|
|
|
102
|
+
/** Bridge activation before tool capability ceilings are applied. */
|
|
103
|
+
export type SubagentLaunchContractIntercomBridge =
|
|
104
|
+
| { active: true; mode: Exclude<IntercomBridgeMode, "off"> }
|
|
105
|
+
| { active: false; mode: IntercomBridgeMode };
|
|
106
|
+
|
|
85
107
|
export interface SubagentLaunchContractAgentCandidate {
|
|
86
108
|
name: string;
|
|
87
109
|
localName?: string;
|
|
@@ -162,6 +184,7 @@ export interface SubagentLaunchContract {
|
|
|
162
184
|
inheritSkills: boolean;
|
|
163
185
|
skills: SubagentLaunchContractSkills;
|
|
164
186
|
tools: SubagentLaunchContractTools;
|
|
187
|
+
intercomBridge: SubagentLaunchContractIntercomBridge;
|
|
165
188
|
roots: SubagentLaunchContractRoots;
|
|
166
189
|
protocol: {
|
|
167
190
|
lifecycleArtifactVersion: number;
|
|
@@ -257,6 +280,15 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
257
280
|
if (input.artifactDir !== undefined && input.artifactDir !== "project" && input.artifactDir !== "session" && input.artifactDir !== "temp") {
|
|
258
281
|
return { ok: false, code: "invalid_artifact_dir", message: `Unsupported artifactDir '${String(input.artifactDir)}'; expected 'project', 'session', or 'temp'.`, diagnostics };
|
|
259
282
|
}
|
|
283
|
+
const bridgeOverride = input.intercomBridge === undefined ? undefined : validateIntercomBridgeConfig({ value: input.intercomBridge, label: "intercomBridge" });
|
|
284
|
+
if (bridgeOverride && !bridgeOverride.ok) {
|
|
285
|
+
return { ok: false, code: "invalid_intercom_bridge", message: bridgeOverride.error, diagnostics };
|
|
286
|
+
}
|
|
287
|
+
// Execution always derives a non-empty target, so an empty one here would
|
|
288
|
+
// silently deactivate the bridge and break parity instead of proving it.
|
|
289
|
+
if (input.orchestratorTarget !== undefined && (typeof input.orchestratorTarget !== "string" || !input.orchestratorTarget.trim())) {
|
|
290
|
+
return { ok: false, code: "invalid_intercom_bridge", message: "orchestratorTarget must be a non-empty string when provided.", diagnostics };
|
|
291
|
+
}
|
|
260
292
|
const scope = resolveExecutionAgentScope(input.agentScope);
|
|
261
293
|
const parentProvider = input.preferredProvider ?? input.parentModel?.provider;
|
|
262
294
|
const discovery = discoverAgentSnapshot(effectiveCwd, scope, parentProvider, { includeChains: false });
|
|
@@ -276,20 +308,32 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
276
308
|
if (!resolvedAgent.agent) {
|
|
277
309
|
return { ok: false, code: "missing_agent", message: formatUnknownAgentError(input.agent, unknownAgentDiagnosticContext(discovered)), diagnostics };
|
|
278
310
|
}
|
|
279
|
-
const
|
|
311
|
+
const definitionAgent = resolvedAgent.agent;
|
|
280
312
|
let extensionBindings: ExtensionBindings | undefined;
|
|
281
313
|
try {
|
|
282
314
|
extensionBindings = normalizeExtensionBindings(input.extensionBindings)?.value;
|
|
283
315
|
} catch (error) {
|
|
284
316
|
return { ok: false, code: "invalid_extension_bindings", message: error instanceof Error ? error.message : String(error), diagnostics };
|
|
285
317
|
}
|
|
286
|
-
if (extensionBindings !== undefined && (
|
|
287
|
-
return { ok: false, code: "unsupported_mode", message: `extensionBindings is not supported for runner.type='${
|
|
318
|
+
if (extensionBindings !== undefined && (definitionAgent.runner?.type === "external-cli" || definitionAgent.runner?.type === "external-job")) {
|
|
319
|
+
return { ok: false, code: "unsupported_mode", message: `extensionBindings is not supported for runner.type='${definitionAgent.runner.type}'.`, diagnostics };
|
|
288
320
|
}
|
|
289
|
-
const context = resolveLaunchContractContext(input,
|
|
321
|
+
const context = resolveLaunchContractContext(input, definitionAgent);
|
|
290
322
|
if (context === "fork") {
|
|
291
|
-
diagnostics.push({ code: "host_required", severity: "host-required", message: "Exact fork session branching
|
|
323
|
+
diagnostics.push({ code: "host_required", severity: "host-required", message: "Exact fork session branching requires Pi host session snapshots." });
|
|
324
|
+
}
|
|
325
|
+
// Execution rewrites the discovered agent through the bridge before any
|
|
326
|
+
// other launch resolution, so preflight must hash the same rewritten agent.
|
|
327
|
+
const bridge = resolveIntercomBridge({
|
|
328
|
+
config: loadConfig().intercomBridge,
|
|
329
|
+
...(bridgeOverride ? { override: bridgeOverride.value } : {}),
|
|
330
|
+
context,
|
|
331
|
+
orchestratorTarget: input.orchestratorTarget ?? PREFLIGHT_ORCHESTRATOR_TARGET,
|
|
332
|
+
});
|
|
333
|
+
if (bridge.active && bridge.interpolatesOrchestratorTarget && input.orchestratorTarget === undefined) {
|
|
334
|
+
diagnostics.push({ code: "host_required", severity: "host-required", message: "The intercomBridge instruction file names the supervisor session; supply orchestratorTarget to bind the exact child prompt." });
|
|
292
335
|
}
|
|
336
|
+
const agent = applyIntercomBridgeToAgent(definitionAgent, bridge);
|
|
293
337
|
const effectiveCapabilityCeiling = intersectSubagentCapabilityCeilings(input.capabilityCeiling, input.inheritedCapabilityCeiling);
|
|
294
338
|
const restrictionMessage = capabilityCeilingAgentRestrictionMessage(agent.name, effectiveCapabilityCeiling);
|
|
295
339
|
if (restrictionMessage) return { ok: false, code: "restricted_agent", message: restrictionMessage, diagnostics };
|
|
@@ -371,6 +415,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
371
415
|
capabilityCeiling: effectiveCapabilityCeiling,
|
|
372
416
|
agentName: agent.name,
|
|
373
417
|
permissionRules,
|
|
418
|
+
hostAvailableBuiltins: input.hostAvailableBuiltins,
|
|
374
419
|
});
|
|
375
420
|
} catch (error) {
|
|
376
421
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -399,17 +444,23 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
399
444
|
if (resolvedSkills.missing.length > 0) {
|
|
400
445
|
return { ok: false, code: "missing_skill", message: `Missing skills: ${resolvedSkills.missing.join(", ")}`, diagnostics };
|
|
401
446
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
447
|
+
const effectiveThinking = resolveEffectiveThinking(model, effectiveThinkingConfig);
|
|
448
|
+
const binding = resolveLaunchBinding({
|
|
449
|
+
agent,
|
|
450
|
+
task: input.task ?? "",
|
|
451
|
+
modelCandidates,
|
|
452
|
+
...(fast !== undefined ? { fast } : {}),
|
|
453
|
+
...(effectiveThinking ? { thinking: effectiveThinking } : {}),
|
|
454
|
+
systemPrompt: buildEffectiveSystemPrompt({ agent, resolvedSkills: resolvedSkills.resolved, cwd: effectiveCwd, ...(outputPath ? { outputPath } : {}) }),
|
|
455
|
+
skills: requestedSkills,
|
|
456
|
+
toolPlan,
|
|
457
|
+
...(outputPath ? { outputPath } : {}),
|
|
458
|
+
outputMode: behavior.outputMode,
|
|
459
|
+
...(input.outputSchema ? { structuredOutputSchema: input.outputSchema } : {}),
|
|
460
|
+
...(extensionBindings ? { extensionBindings } : {}),
|
|
461
|
+
});
|
|
410
462
|
const candidates = candidateList(input.agent, agent, discovery.all);
|
|
411
463
|
const shadowedCandidates = candidates.filter((candidate) => !candidate.selected);
|
|
412
|
-
const definitionDigest = agentDefinitionDigest(agent);
|
|
413
464
|
const contractBase: Omit<SubagentLaunchContract, "digest"> = {
|
|
414
465
|
version: SUBAGENT_LAUNCH_CONTRACT_VERSION,
|
|
415
466
|
runId,
|
|
@@ -420,13 +471,13 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
420
471
|
source: agent.source,
|
|
421
472
|
filePath: agent.filePath,
|
|
422
473
|
definitionProjectionVersion: AGENT_DEFINITION_PROJECTION_VERSION,
|
|
423
|
-
definitionDigest,
|
|
474
|
+
definitionDigest: binding.definitionDigest,
|
|
424
475
|
shadowedCandidates,
|
|
425
476
|
},
|
|
426
477
|
context,
|
|
427
478
|
...(model ? { model } : {}),
|
|
428
479
|
modelCandidates,
|
|
429
|
-
...(
|
|
480
|
+
...(effectiveThinking ? { thinking: effectiveThinking } : {}),
|
|
430
481
|
...(thinkingCeiling ? { thinkingCeiling } : {}),
|
|
431
482
|
systemPromptMode: agent.systemPromptMode,
|
|
432
483
|
inheritProjectContext: agent.inheritProjectContext,
|
|
@@ -456,6 +507,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
456
507
|
...(toolPlan.capabilityCeiling ? { capabilityCeiling: toolPlan.capabilityCeiling } : {}),
|
|
457
508
|
...(toolPlan.capabilityAudit ? { capabilityAudit: toolPlan.capabilityAudit } : {}),
|
|
458
509
|
},
|
|
510
|
+
intercomBridge: bridge.active && bridge.mode !== "off" ? { active: true, mode: bridge.mode } : { active: false, mode: bridge.mode },
|
|
459
511
|
roots: {
|
|
460
512
|
cwd: effectiveCwd,
|
|
461
513
|
...(sessionRoot ? { sessionRoot } : {}),
|
|
@@ -477,28 +529,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
477
529
|
packageVersion: packageVersion(),
|
|
478
530
|
},
|
|
479
531
|
diagnostics,
|
|
480
|
-
launchContractDigest:
|
|
481
|
-
task: input.task ?? "",
|
|
482
|
-
definitionDigest,
|
|
483
|
-
...(model ? { model } : {}),
|
|
484
|
-
modelCandidates,
|
|
485
|
-
...(fast !== undefined ? { fast } : {}),
|
|
486
|
-
...(resolveEffectiveThinking(model, effectiveThinkingConfig) ? { thinking: resolveEffectiveThinking(model, effectiveThinkingConfig) } : {}),
|
|
487
|
-
systemPrompt: effectiveSystemPrompt,
|
|
488
|
-
systemPromptMode: agent.systemPromptMode,
|
|
489
|
-
inheritProjectContext: agent.inheritProjectContext,
|
|
490
|
-
inheritGlobalContext: agent.inheritGlobalContext,
|
|
491
|
-
inheritSkills: agent.inheritSkills,
|
|
492
|
-
skills: requestedSkills,
|
|
493
|
-
tools: toolPlan.effectiveToolAllowlist,
|
|
494
|
-
...(toolPlan.excludeTools.length > 0 ? { excludeTools: toolPlan.excludeTools } : {}),
|
|
495
|
-
extensions: toolPlan.extensionArgs,
|
|
496
|
-
mcpDirectTools: toolPlan.effectiveMcpTools,
|
|
497
|
-
...(outputPath ? { outputPath } : {}),
|
|
498
|
-
outputMode: behavior.outputMode,
|
|
499
|
-
...(input.outputSchema ? { structuredOutputSchema: input.outputSchema } : {}),
|
|
500
|
-
...(extensionBindings ? { extensionBindings } : {}),
|
|
501
|
-
}),
|
|
532
|
+
launchContractDigest: binding.launchContractDigest,
|
|
502
533
|
};
|
|
503
534
|
return { ok: true, contract: { ...contractBase, digest: digestContract(contractBase) } };
|
|
504
535
|
}
|
package/src/api/shared-types.ts
CHANGED
|
@@ -13,6 +13,8 @@ export {
|
|
|
13
13
|
type ExecutionProjection,
|
|
14
14
|
type ExternalJobRunnerStatus,
|
|
15
15
|
type ExternalJobStatus,
|
|
16
|
+
type IntercomBridgeConfig,
|
|
17
|
+
type IntercomBridgeMode,
|
|
16
18
|
type JsonSchemaObject,
|
|
17
19
|
type OutputMode,
|
|
18
20
|
type ReviewProjection,
|
|
@@ -23,5 +25,5 @@ export {
|
|
|
23
25
|
type ManagedWorktreeProvider,
|
|
24
26
|
type WorktreeNaming,
|
|
25
27
|
type WorktreeProvider,
|
|
26
|
-
type
|
|
28
|
+
type WorkflowResourceProvenance,
|
|
27
29
|
} from "../shared/types.ts";
|
|
@@ -9,11 +9,13 @@ import { resolveWaitToolConfig } from "../runs/background/wait-config.ts";
|
|
|
9
9
|
import type { ChildRuntimeConfig } from "../runs/shared/child-runtime-config.ts";
|
|
10
10
|
import { readNestedControlRequests, resolveInheritedNestedRoute, type NestedRoute, writeNestedControlResult } from "../runs/shared/nested-events.ts";
|
|
11
11
|
import { deliverSubagentIntercomMessageEvent } from "../intercom/result-intercom.ts";
|
|
12
|
+
import { createNativeSupervisorChannel, NATIVE_SUPERVISOR_TOOL_NAME, resolveSupervisorChannelDir } from "../intercom/native-supervisor-channel.ts";
|
|
13
|
+
import { readStatus } from "../shared/utils.ts";
|
|
12
14
|
import { resolveSubagentIntercomTarget } from "../intercom/intercom-bridge.ts";
|
|
13
15
|
import { createSubagentParamsSchema } from "./schemas.ts";
|
|
14
16
|
import { finalizeToolResult } from "./tool-result.ts";
|
|
15
17
|
import { loadConfig, resolveAsyncByDefault } from "./config.ts";
|
|
16
|
-
import { type Details, type SubagentState } from "../shared/types.ts";
|
|
18
|
+
import { SUBAGENT_ASYNC_STARTED_EVENT, type AsyncStartedEvent, type Details, type SubagentState } from "../shared/types.ts";
|
|
17
19
|
|
|
18
20
|
function getSubagentSessionRoot(parentSessionFile: string | null): string {
|
|
19
21
|
if (parentSessionFile) {
|
|
@@ -28,7 +30,7 @@ function expandTilde(p: string): string {
|
|
|
28
30
|
return p.startsWith("~/") ? path.join(os.homedir(), p.slice(2)) : p;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
function createChildSafeState(): SubagentState {
|
|
33
|
+
export function createChildSafeState(): SubagentState {
|
|
32
34
|
return {
|
|
33
35
|
baseCwd: "",
|
|
34
36
|
currentSessionId: null,
|
|
@@ -141,7 +143,7 @@ function startNestedControlInboxListener(pi: ExtensionAPI, state: SubagentState,
|
|
|
141
143
|
return () => clearInterval(timer);
|
|
142
144
|
}
|
|
143
145
|
|
|
144
|
-
/** Register
|
|
146
|
+
/** Register delegation and supervisor replies for fanout-authorized children. */
|
|
145
147
|
export default function registerFanoutChildSubagentExtension(pi: ExtensionAPI, childConfig: ChildRuntimeConfig): void {
|
|
146
148
|
if (!childConfig.fanoutChild) return;
|
|
147
149
|
|
|
@@ -156,7 +158,38 @@ export default function registerFanoutChildSubagentExtension(pi: ExtensionAPI, c
|
|
|
156
158
|
|
|
157
159
|
const config = loadConfig();
|
|
158
160
|
const waitToolConfig = resolveWaitToolConfig(config.waitTool);
|
|
159
|
-
const state = createChildSafeState();
|
|
161
|
+
const state = childConfig.runtimeState ?? createChildSafeState();
|
|
162
|
+
const asyncChildren = new Map<string, { dir: string; agents: string[] }>();
|
|
163
|
+
const foregroundChannels = new Set<string>();
|
|
164
|
+
const supervisorChannel = createNativeSupervisorChannel(pi, state, {
|
|
165
|
+
getChannelDirs: () => {
|
|
166
|
+
const dirs = new Set<string>();
|
|
167
|
+
for (const run of state.foregroundControls.values()) {
|
|
168
|
+
for (const child of run.activeChildren?.values() ?? []) dirs.add(resolveSupervisorChannelDir(run.runId, child.agent, child.index));
|
|
169
|
+
}
|
|
170
|
+
for (const run of state.foregroundRuns?.values() ?? []) {
|
|
171
|
+
for (const child of run.children) {
|
|
172
|
+
if (child.status === "detached") dirs.add(resolveSupervisorChannelDir(run.runId, child.agent, child.index));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const retiringForeground = [...foregroundChannels].filter(dir => !dirs.has(dir));
|
|
176
|
+
for (const dir of dirs) foregroundChannels.add(dir);
|
|
177
|
+
for (const dir of retiringForeground) dirs.add(dir);
|
|
178
|
+
const retiringAsync: string[] = [];
|
|
179
|
+
for (const [id, child] of asyncChildren) {
|
|
180
|
+
const status = readStatus(child.dir);
|
|
181
|
+
if (status && status.state !== "queued" && status.state !== "running") retiringAsync.push(id);
|
|
182
|
+
for (const [index, agent] of child.agents.entries()) dirs.add(resolveSupervisorChannelDir(id, agent, index));
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
dirs: [...dirs],
|
|
186
|
+
retire: () => {
|
|
187
|
+
for (const dir of retiringForeground) foregroundChannels.delete(dir);
|
|
188
|
+
for (const id of retiringAsync) asyncChildren.delete(id);
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
},
|
|
192
|
+
});
|
|
160
193
|
const executor = createSubagentExecutor({
|
|
161
194
|
pi,
|
|
162
195
|
state,
|
|
@@ -170,6 +203,8 @@ export default function registerFanoutChildSubagentExtension(pi: ExtensionAPI, c
|
|
|
170
203
|
discoverAgents,
|
|
171
204
|
allowMutatingManagementActions: false,
|
|
172
205
|
childRuntime: childConfig,
|
|
206
|
+
activateSupervisorTransport: supervisorChannel.activateTransport,
|
|
207
|
+
findPendingAsks: supervisorChannel.findPendingAsks,
|
|
173
208
|
});
|
|
174
209
|
|
|
175
210
|
const params = createSubagentParamsSchema();
|
|
@@ -188,6 +223,30 @@ export default function registerFanoutChildSubagentExtension(pi: ExtensionAPI, c
|
|
|
188
223
|
};
|
|
189
224
|
|
|
190
225
|
pi.registerTool(tool);
|
|
226
|
+
let unsubscribeAsyncStarted: (() => void) | undefined;
|
|
227
|
+
pi.on("session_start", (_event, ctx) => {
|
|
228
|
+
supervisorChannel.registerTools();
|
|
229
|
+
// The host applies the explicit allowlist to dynamic registration too.
|
|
230
|
+
if (!pi.getAllTools().some(tool => tool.name === NATIVE_SUPERVISOR_TOOL_NAME)) return;
|
|
231
|
+
// Downward asks belong to this coordinator, not its parent or persisted session file.
|
|
232
|
+
state.supervisorOwnerSessionId = ctx.sessionManager.getSessionId() || null;
|
|
233
|
+
unsubscribeAsyncStarted = pi.events.on(SUBAGENT_ASYNC_STARTED_EVENT, (payload: unknown) => {
|
|
234
|
+
const info = payload as AsyncStartedEvent;
|
|
235
|
+
if (!info.id || !info.asyncDir || info.sessionId !== state.currentSessionId) return;
|
|
236
|
+
const agents = info.agents ?? (info.agent ? [info.agent] : []);
|
|
237
|
+
asyncChildren.set(info.id, { dir: info.asyncDir, agents });
|
|
238
|
+
supervisorChannel.activateTransport();
|
|
239
|
+
});
|
|
240
|
+
supervisorChannel.start();
|
|
241
|
+
supervisorChannel.activateTransport();
|
|
242
|
+
});
|
|
243
|
+
pi.on("session_shutdown", () => {
|
|
244
|
+
unsubscribeAsyncStarted?.();
|
|
245
|
+
asyncChildren.clear();
|
|
246
|
+
foregroundChannels.clear();
|
|
247
|
+
supervisorChannel.dispose();
|
|
248
|
+
state.supervisorOwnerSessionId = null;
|
|
249
|
+
});
|
|
191
250
|
const route = resolveNestedControlRoute(childConfig);
|
|
192
251
|
if (!route) return;
|
|
193
252
|
const listenerCleanupKey = "__piSubagentFanoutChildNestedControlInboxCleanups";
|
package/src/extension/index.ts
CHANGED
|
@@ -19,7 +19,8 @@ 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 { discoverAgentSnapshot, discoverAgents, type AgentConfig, type AgentScope } from "../agents/agents.ts";
|
|
22
|
+
import { clearAgentDiscoveryCache, discoverAgentSnapshot, discoverAgents, type AgentConfig, type AgentScope } from "../agents/agents.ts";
|
|
23
|
+
import { appendAdvertisedAgentPrompt, buildAdvertisedAgentPrompt } from "../agents/advertised-agent-prompt.ts";
|
|
23
24
|
import { clearRuntimeAgentsForPi, listRuntimeAgentConfigs, mergeRuntimeAgents } from "../agents/runtime-agent-registry.ts";
|
|
24
25
|
import { registerRuntimeAgentEventListener } from "../agents/runtime-agent-events.ts";
|
|
25
26
|
import { ensureAccessibleDir } from "../shared/accessible-dir.ts";
|
|
@@ -29,8 +30,9 @@ import { getAgentDir } from "../shared/utils.ts";
|
|
|
29
30
|
import { isStaleExtensionContextError, withCachedUiContext } from "../shared/extension-context.ts";
|
|
30
31
|
import { currentCompletionOwnerId } from "../shared/completion-owner.ts";
|
|
31
32
|
import { cleanupOldChainDirs } from "../shared/settings.ts";
|
|
32
|
-
import { clearLegacyResultAnimationTimer, renderSubagentResult, renderSubagentSummary } from "../tui/render.ts";
|
|
33
|
+
import { clearLegacyResultAnimationTimer, renderSubagentResult, renderSubagentSummary, setInlineWorkflowCoverage } from "../tui/render.ts";
|
|
33
34
|
import { openSubagentFleet } from "../tui/fleet.ts";
|
|
35
|
+
import { createBuiltinInspectorPlugins } from "../inspectors/plugins.ts";
|
|
34
36
|
import { SubagentFleetStatus, resolveFleetViewPlacement } from "../tui/fleet-status.ts";
|
|
35
37
|
import { createSubagentParamsSchema } from "./schemas.ts";
|
|
36
38
|
import { createSubagentExecutor, type SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
@@ -335,14 +337,18 @@ function createSlashResultComponent(
|
|
|
335
337
|
theme: ExtensionContext["ui"]["theme"],
|
|
336
338
|
rendererConfig?: MainWindowRendererConfig,
|
|
337
339
|
foregroundDetachShortcut?: string,
|
|
340
|
+
getCurrentTheme: () => ExtensionContext["ui"]["theme"] = () => theme,
|
|
338
341
|
): Container {
|
|
339
342
|
const container = new Container();
|
|
340
343
|
let lastVersion = -1;
|
|
344
|
+
let lastTheme: ExtensionContext["ui"]["theme"] | undefined;
|
|
341
345
|
container.render = (width: number): string[] => {
|
|
342
346
|
const snapshot = getSlashRenderableSnapshot(details);
|
|
343
|
-
|
|
347
|
+
const currentTheme = getCurrentTheme();
|
|
348
|
+
if (snapshot.version !== lastVersion || currentTheme !== lastTheme || isSlashResultRunning(snapshot.result)) {
|
|
344
349
|
lastVersion = snapshot.version;
|
|
345
|
-
|
|
350
|
+
lastTheme = currentTheme;
|
|
351
|
+
rebuildSlashResultContainer(container, snapshot.result, options, currentTheme, rendererConfig, foregroundDetachShortcut);
|
|
346
352
|
}
|
|
347
353
|
return Container.prototype.render.call(container, width);
|
|
348
354
|
};
|
|
@@ -489,7 +495,9 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
489
495
|
}, run);
|
|
490
496
|
};
|
|
491
497
|
|
|
492
|
-
const supervisorChannel = createNativeSupervisorChannel(pi, state
|
|
498
|
+
const supervisorChannel = createNativeSupervisorChannel(pi, state, {
|
|
499
|
+
getCurrentOwnerStates: () => executor.getCurrentSupervisorOwnerStates(),
|
|
500
|
+
});
|
|
493
501
|
const waitSubscriptionManager = createWaitSubscriptionManager(pi, state);
|
|
494
502
|
const mainWatchdog = registerMainWatchdog(pi);
|
|
495
503
|
const resultDeliveryOwnership = createResultDeliveryOwnership(state);
|
|
@@ -500,7 +508,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
500
508
|
const ctx = withLastUiContext((current) => current);
|
|
501
509
|
if (!ctx) return;
|
|
502
510
|
try {
|
|
503
|
-
await openSubagentFleet(ctx, state, { initialKey: itemKey, asyncDirRoot: DIRS.async, resultsDir: DIRS.results, fleetKeybindings: config.fleetKeybindings });
|
|
511
|
+
await openSubagentFleet(ctx, state, { initialKey: itemKey, asyncDirRoot: DIRS.async, resultsDir: DIRS.results, fleetKeybindings: config.fleetKeybindings, inspectorPlugins: createBuiltinInspectorPlugins() });
|
|
504
512
|
} catch (error) {
|
|
505
513
|
if (isStaleExtensionContextError(error)) {
|
|
506
514
|
if (state.lastUiContext === ctx) state.lastUiContext = null;
|
|
@@ -508,7 +516,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
508
516
|
}
|
|
509
517
|
throw error;
|
|
510
518
|
}
|
|
511
|
-
}, { placement: fleetViewPlacement })
|
|
519
|
+
}, { placement: fleetViewPlacement, onWorkflowCoverageChange: setInlineWorkflowCoverage })
|
|
512
520
|
: undefined;
|
|
513
521
|
let executorScheduled: ((id: string, params: SubagentParamsLike, signal: AbortSignal, ctx: ExtensionContext) => Promise<AgentToolResult<Details>>) | undefined;
|
|
514
522
|
let goalTurnId = 0;
|
|
@@ -531,6 +539,15 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
531
539
|
resolveCapabilityCeiling: (sessionId) => resolveCurrentSubagentCapabilityCeiling(sessionId),
|
|
532
540
|
});
|
|
533
541
|
let refreshResultDelivery = () => {};
|
|
542
|
+
let advertisedAgents: AgentConfig[] = [];
|
|
543
|
+
let advertisedContext: Pick<ExtensionContext, "cwd" | "model"> | undefined;
|
|
544
|
+
const refreshAdvertisedAgents = () => {
|
|
545
|
+
advertisedAgents = [];
|
|
546
|
+
if (!advertisedContext) return;
|
|
547
|
+
clearAgentDiscoveryCache();
|
|
548
|
+
advertisedAgents = discoverAgents(advertisedContext.cwd, "both", advertisedContext.model?.provider).agents
|
|
549
|
+
.filter((agent) => agent.advertise === true);
|
|
550
|
+
};
|
|
534
551
|
const hasResultDeliveryDemand = () => {
|
|
535
552
|
if ([...state.asyncJobs.values()].some((job) => job.status === "queued" || job.status === "running")) return true;
|
|
536
553
|
if (state.foregroundControls.size > 0) return true;
|
|
@@ -609,7 +626,16 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
609
626
|
getSubagentSessionRoot,
|
|
610
627
|
expandTilde,
|
|
611
628
|
discoverAgents: discoverAgentsForRuntime,
|
|
629
|
+
onAgentsChanged: () => {
|
|
630
|
+
try {
|
|
631
|
+
refreshAdvertisedAgents();
|
|
632
|
+
} catch (error) {
|
|
633
|
+
// The mutation already persisted. Withdraw stale guidance, not its result.
|
|
634
|
+
console.error("Failed to refresh advertised agents; catalog withdrawn until refresh:", error);
|
|
635
|
+
}
|
|
636
|
+
},
|
|
612
637
|
activateSupervisorTransport: () => supervisorChannel.activateTransport(),
|
|
638
|
+
findPendingAsks: (target) => supervisorChannel.findPendingAsks(target),
|
|
613
639
|
refreshResultDelivery: () => refreshResultDelivery(),
|
|
614
640
|
trackRetainedNestedRoute: undefined,
|
|
615
641
|
};
|
|
@@ -627,7 +653,14 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
627
653
|
pi.registerMessageRenderer<SlashMessageDetails>(SLASH_RESULT_TYPE, (message, options, theme) => {
|
|
628
654
|
const details = resolveSlashMessageDetails(message.details);
|
|
629
655
|
if (!details) return undefined;
|
|
630
|
-
return createSlashResultComponent(
|
|
656
|
+
return createSlashResultComponent(
|
|
657
|
+
details,
|
|
658
|
+
options,
|
|
659
|
+
theme,
|
|
660
|
+
config.mainWindowRenderer,
|
|
661
|
+
config.foregroundDetachShortcut,
|
|
662
|
+
() => state.lastUiContext?.ui.theme ?? theme,
|
|
663
|
+
);
|
|
631
664
|
});
|
|
632
665
|
|
|
633
666
|
pi.registerMessageRenderer<undefined>(SLASH_TEXT_RESULT_TYPE, (message, _options, _theme) => {
|
|
@@ -778,6 +811,16 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
778
811
|
|
|
779
812
|
pi.registerTool(tool);
|
|
780
813
|
|
|
814
|
+
pi.on("before_agent_start", (event, ctx) => {
|
|
815
|
+
const selectedTools = event.systemPromptOptions?.selectedTools ?? (typeof pi.getActiveTools === "function" ? pi.getActiveTools() : []);
|
|
816
|
+
const sessionId = state.currentSessionId ?? resolveCurrentSessionId(ctx.sessionManager);
|
|
817
|
+
const advertisedPrompt = Array.isArray(selectedTools) && selectedTools.includes("subagent")
|
|
818
|
+
? buildAdvertisedAgentPrompt(advertisedAgents, resolveCurrentSubagentCapabilityCeiling(sessionId))
|
|
819
|
+
: undefined;
|
|
820
|
+
const systemPrompt = appendAdvertisedAgentPrompt(event.systemPrompt, advertisedPrompt);
|
|
821
|
+
if (systemPrompt !== event.systemPrompt) return { systemPrompt };
|
|
822
|
+
});
|
|
823
|
+
|
|
781
824
|
registerWaitTool(pi, state, waitToolConfig.enabled, waitSubscriptionManager, waitToolConfig.defaultTimeoutMs);
|
|
782
825
|
|
|
783
826
|
pi.on("agent_end", async (_event, ctx) => {
|
|
@@ -912,6 +955,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
912
955
|
const previousRuntimeSessionId = state.currentSessionId;
|
|
913
956
|
resultDeliveryOwnership.claimPredecessor(previousSessionFile, previousRuntimeSessionId);
|
|
914
957
|
state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
958
|
+
state.supervisorOwnerSessionId = ctx.sessionManager.getSessionId() || null;
|
|
915
959
|
transitionResultDelivery();
|
|
916
960
|
state.parentSessionFile = ctx.sessionManager.getSessionFile();
|
|
917
961
|
state.trustedSessionFileRoot = state.parentSessionFile ? path.join(getAgentDir(), "sessions") : undefined;
|
|
@@ -1029,6 +1073,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
1029
1073
|
promptTemplateBridge.dispose();
|
|
1030
1074
|
state.widgetsSuspended = false;
|
|
1031
1075
|
state.currentSessionId = null;
|
|
1076
|
+
state.supervisorOwnerSessionId = null;
|
|
1032
1077
|
state.statusProjectionSessionId = null;
|
|
1033
1078
|
state.parentSessionFile = null;
|
|
1034
1079
|
parentSessionEnvValue = null;
|
|
@@ -1141,4 +1186,9 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
1141
1186
|
}
|
|
1142
1187
|
await herdrStatusBridge.flush();
|
|
1143
1188
|
});
|
|
1189
|
+
|
|
1190
|
+
pi.on("session_start", (_event, ctx) => {
|
|
1191
|
+
advertisedContext = { cwd: ctx.cwd, model: ctx.model };
|
|
1192
|
+
refreshAdvertisedAgents();
|
|
1193
|
+
});
|
|
1144
1194
|
}
|
|
@@ -73,7 +73,6 @@ export function normalizePublicSubagentExecution<T extends PublicSubagentExecuti
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
if (params.baseRef !== undefined) {
|
|
76
|
-
if (typeof params.baseRef !== "string") return { ok: false, error: "baseRef must be a valid Git ref.", mode: params.action === undefined ? "workflow" : "management" };
|
|
77
76
|
try {
|
|
78
77
|
normalizeWorktreeBaseRef(params.baseRef);
|
|
79
78
|
} catch (error) {
|
|
@@ -98,8 +97,10 @@ export function normalizePublicSubagentExecution<T extends PublicSubagentExecuti
|
|
|
98
97
|
if (hasCapacityOverride) {
|
|
99
98
|
const capacityOverrideError = validateWorkflowCapacityOverrides(params);
|
|
100
99
|
if (capacityOverrideError) return { ok: false, error: capacityOverrideError, mode: params.action === undefined ? "workflow" : "management" };
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
const validatesSpawnBudget = typeof params.action === "string" && params.action.trim() === "validate"
|
|
101
|
+
&& params.globalConcurrencyLimit === undefined && params.maxSubagentSpawnsPerRun !== undefined;
|
|
102
|
+
if ((params.action !== undefined && !validatesSpawnBudget) || hasNamedWorkflow || (params.workflowScript === undefined && params.workflowScriptPath === undefined)) {
|
|
103
|
+
return { ok: false, error: "Workflow capacity overrides are only supported on top-level workflowScript or workflowScriptPath calls; validate accepts maxSubagentSpawnsPerRun for static budget checks.", mode: params.action === undefined ? "workflow" : "management" };
|
|
103
104
|
}
|
|
104
105
|
}
|
|
105
106
|
if (params.preflight !== undefined && !hasWorkflowInput) {
|