pi-subagents 0.66.0 → 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 +68 -0
- package/README.md +4 -3
- package/agents/evidence-auditor.md +34 -0
- package/agents/reviewer.md +3 -2
- package/docs/agents.md +6 -3
- package/docs/configuration.md +7 -5
- package/docs/extension-api.md +33 -18
- package/docs/missions.md +8 -0
- package/docs/models.md +1 -1
- package/docs/observability.md +4 -4
- package/docs/standalone-background.md +49 -0
- package/docs/tool-reference.md +18 -8
- package/docs/watchdog.md +35 -4
- package/docs/workflows.md +26 -12
- package/inspector-runner.mjs +2 -2
- package/package.json +1 -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 +7 -5
- package/skills/pi-subagents/references/prompting-and-roles.md +2 -2
- package/src/agents/advertised-agent-prompt.ts +34 -3
- package/src/agents/agents.ts +6 -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 +2 -0
- package/src/extension/fanout-child.ts +63 -4
- package/src/extension/index.ts +20 -8
- package/src/extension/public-execution.ts +4 -2
- package/src/extension/rpc.ts +4 -0
- package/src/extension/schemas.ts +67 -78
- package/src/extension/tool-description.ts +29 -82
- 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 +22 -13
- package/src/runs/background/active-async-capacity.ts +4 -0
- package/src/runs/background/async-execution.ts +45 -56
- package/src/runs/background/async-resume.ts +5 -9
- package/src/runs/background/auto-drain.ts +6 -3
- package/src/runs/background/binary-bootstrap.ts +33 -0
- package/src/runs/background/fleet-view.ts +30 -2
- package/src/runs/background/notify.ts +31 -1
- package/src/runs/background/owned-process-tree.ts +29 -2
- package/src/runs/background/run-child-session.ts +61 -4
- package/src/runs/background/run-status.ts +3 -0
- package/src/runs/background/runner-aliases.ts +11 -3
- package/src/runs/background/runner-child-launch.ts +2 -0
- package/src/runs/background/runner-child-sessions.ts +5 -4
- package/src/runs/background/scheduled-runs.ts +40 -13
- package/src/runs/background/steering.ts +20 -2
- package/src/runs/background/subagent-runner.ts +47 -31
- package/src/runs/background/subagent-wait.ts +51 -8
- package/src/runs/background/wait-tool.ts +1 -1
- package/src/runs/foreground/async-steering-action.ts +18 -7
- package/src/runs/foreground/execution.ts +44 -31
- package/src/runs/foreground/prompt-audit.ts +3 -1
- package/src/runs/foreground/subagent-executor.ts +110 -100
- package/src/runs/foreground/workflow-detach-reconcile.ts +2 -0
- package/src/runs/foreground/workflow-foreground-steering.ts +2 -1
- package/src/runs/shared/acceptance.ts +5 -2
- package/src/runs/shared/async-status-projection.ts +4 -0
- package/src/runs/shared/capability-ceiling.ts +2 -0
- package/src/runs/shared/child-hooks.ts +25 -10
- package/src/runs/shared/child-launch.ts +12 -2
- 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 +33 -2
- package/src/runs/shared/child-tool-plan.ts +122 -3
- package/src/runs/shared/completion-guard.ts +5 -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/llm-intent-arbiter.ts +12 -3
- package/src/runs/shared/model-fallback.ts +2 -0
- package/src/runs/shared/orca-progress-tabs.ts +1 -1
- package/src/runs/shared/pi-spawn.ts +10 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +9 -3
- 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.ts +42 -12
- 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 +4 -1
- 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 +16 -14
- package/src/tui/render.ts +149 -28
- 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/scripted-workflow.ts +237 -7
- package/src/workflows/workflow-checklist.ts +2 -2
- /package/src/inspectors/{herdr/shell-command.ts → shell-command.ts} +0 -0
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
|
@@ -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
|
@@ -30,8 +30,9 @@ import { getAgentDir } from "../shared/utils.ts";
|
|
|
30
30
|
import { isStaleExtensionContextError, withCachedUiContext } from "../shared/extension-context.ts";
|
|
31
31
|
import { currentCompletionOwnerId } from "../shared/completion-owner.ts";
|
|
32
32
|
import { cleanupOldChainDirs } from "../shared/settings.ts";
|
|
33
|
-
import { clearLegacyResultAnimationTimer, renderSubagentResult, renderSubagentSummary } from "../tui/render.ts";
|
|
33
|
+
import { clearLegacyResultAnimationTimer, renderSubagentResult, renderSubagentSummary, setInlineWorkflowCoverage } from "../tui/render.ts";
|
|
34
34
|
import { openSubagentFleet } from "../tui/fleet.ts";
|
|
35
|
+
import { createBuiltinInspectorPlugins } from "../inspectors/plugins.ts";
|
|
35
36
|
import { SubagentFleetStatus, resolveFleetViewPlacement } from "../tui/fleet-status.ts";
|
|
36
37
|
import { createSubagentParamsSchema } from "./schemas.ts";
|
|
37
38
|
import { createSubagentExecutor, type SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
@@ -336,14 +337,18 @@ function createSlashResultComponent(
|
|
|
336
337
|
theme: ExtensionContext["ui"]["theme"],
|
|
337
338
|
rendererConfig?: MainWindowRendererConfig,
|
|
338
339
|
foregroundDetachShortcut?: string,
|
|
340
|
+
getCurrentTheme: () => ExtensionContext["ui"]["theme"] = () => theme,
|
|
339
341
|
): Container {
|
|
340
342
|
const container = new Container();
|
|
341
343
|
let lastVersion = -1;
|
|
344
|
+
let lastTheme: ExtensionContext["ui"]["theme"] | undefined;
|
|
342
345
|
container.render = (width: number): string[] => {
|
|
343
346
|
const snapshot = getSlashRenderableSnapshot(details);
|
|
344
|
-
|
|
347
|
+
const currentTheme = getCurrentTheme();
|
|
348
|
+
if (snapshot.version !== lastVersion || currentTheme !== lastTheme || isSlashResultRunning(snapshot.result)) {
|
|
345
349
|
lastVersion = snapshot.version;
|
|
346
|
-
|
|
350
|
+
lastTheme = currentTheme;
|
|
351
|
+
rebuildSlashResultContainer(container, snapshot.result, options, currentTheme, rendererConfig, foregroundDetachShortcut);
|
|
347
352
|
}
|
|
348
353
|
return Container.prototype.render.call(container, width);
|
|
349
354
|
};
|
|
@@ -503,7 +508,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
503
508
|
const ctx = withLastUiContext((current) => current);
|
|
504
509
|
if (!ctx) return;
|
|
505
510
|
try {
|
|
506
|
-
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() });
|
|
507
512
|
} catch (error) {
|
|
508
513
|
if (isStaleExtensionContextError(error)) {
|
|
509
514
|
if (state.lastUiContext === ctx) state.lastUiContext = null;
|
|
@@ -511,7 +516,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
511
516
|
}
|
|
512
517
|
throw error;
|
|
513
518
|
}
|
|
514
|
-
}, { placement: fleetViewPlacement })
|
|
519
|
+
}, { placement: fleetViewPlacement, onWorkflowCoverageChange: setInlineWorkflowCoverage })
|
|
515
520
|
: undefined;
|
|
516
521
|
let executorScheduled: ((id: string, params: SubagentParamsLike, signal: AbortSignal, ctx: ExtensionContext) => Promise<AgentToolResult<Details>>) | undefined;
|
|
517
522
|
let goalTurnId = 0;
|
|
@@ -648,7 +653,14 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
648
653
|
pi.registerMessageRenderer<SlashMessageDetails>(SLASH_RESULT_TYPE, (message, options, theme) => {
|
|
649
654
|
const details = resolveSlashMessageDetails(message.details);
|
|
650
655
|
if (!details) return undefined;
|
|
651
|
-
return createSlashResultComponent(
|
|
656
|
+
return createSlashResultComponent(
|
|
657
|
+
details,
|
|
658
|
+
options,
|
|
659
|
+
theme,
|
|
660
|
+
config.mainWindowRenderer,
|
|
661
|
+
config.foregroundDetachShortcut,
|
|
662
|
+
() => state.lastUiContext?.ui.theme ?? theme,
|
|
663
|
+
);
|
|
652
664
|
});
|
|
653
665
|
|
|
654
666
|
pi.registerMessageRenderer<undefined>(SLASH_TEXT_RESULT_TYPE, (message, _options, _theme) => {
|
|
@@ -800,9 +812,9 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
800
812
|
pi.registerTool(tool);
|
|
801
813
|
|
|
802
814
|
pi.on("before_agent_start", (event, ctx) => {
|
|
803
|
-
const selectedTools = event.systemPromptOptions
|
|
815
|
+
const selectedTools = event.systemPromptOptions?.selectedTools ?? (typeof pi.getActiveTools === "function" ? pi.getActiveTools() : []);
|
|
804
816
|
const sessionId = state.currentSessionId ?? resolveCurrentSessionId(ctx.sessionManager);
|
|
805
|
-
const advertisedPrompt = selectedTools.includes("subagent")
|
|
817
|
+
const advertisedPrompt = Array.isArray(selectedTools) && selectedTools.includes("subagent")
|
|
806
818
|
? buildAdvertisedAgentPrompt(advertisedAgents, resolveCurrentSubagentCapabilityCeiling(sessionId))
|
|
807
819
|
: undefined;
|
|
808
820
|
const systemPrompt = appendAdvertisedAgentPrompt(event.systemPrompt, advertisedPrompt);
|
|
@@ -97,8 +97,10 @@ export function normalizePublicSubagentExecution<T extends PublicSubagentExecuti
|
|
|
97
97
|
if (hasCapacityOverride) {
|
|
98
98
|
const capacityOverrideError = validateWorkflowCapacityOverrides(params);
|
|
99
99
|
if (capacityOverrideError) return { ok: false, error: capacityOverrideError, mode: params.action === undefined ? "workflow" : "management" };
|
|
100
|
-
|
|
101
|
-
|
|
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" };
|
|
102
104
|
}
|
|
103
105
|
}
|
|
104
106
|
if (params.preflight !== undefined && !hasWorkflowInput) {
|
package/src/extension/rpc.ts
CHANGED
|
@@ -499,9 +499,13 @@ function manageParams(params: unknown): SubagentParamsLike {
|
|
|
499
499
|
if (requiresId && typeof input.id !== "string") {
|
|
500
500
|
throw new SubagentRpcError("invalid_params", `RPC manage ${action} requires id.`);
|
|
501
501
|
}
|
|
502
|
+
if (action === "schedule.run" && input.quiet !== undefined && typeof input.quiet !== "boolean") {
|
|
503
|
+
throw new SubagentRpcError("invalid_params", "RPC manage quiet must be a boolean.");
|
|
504
|
+
}
|
|
502
505
|
const output: SubagentParamsLike = {
|
|
503
506
|
action,
|
|
504
507
|
...(typeof input.id === "string" ? { id: input.id.trim() } : {}),
|
|
508
|
+
...(action === "schedule.run" && input.quiet === true ? { quiet: true } : {}),
|
|
505
509
|
};
|
|
506
510
|
assertSubagentParams(output, "RPC manage params");
|
|
507
511
|
return output;
|