pi-subagents 0.35.1 → 0.37.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 +71 -0
- package/README.md +224 -31
- package/agents/advisor.md +73 -0
- package/agents/delegate.md +2 -0
- package/agents/worker.md +2 -0
- package/package.json +11 -13
- package/skills/pi-subagents/SKILL.md +42 -13
- package/src/agents/agent-management.ts +7 -1
- package/src/agents/agents.ts +121 -17
- package/src/api/capability-ceiling.ts +17 -0
- package/src/api/delegation.ts +127 -0
- package/src/api/preflight.ts +399 -0
- package/src/extension/config.ts +7 -1
- package/src/extension/index.ts +52 -38
- package/src/extension/rpc.ts +31 -2
- package/src/extension/schemas.ts +29 -3
- package/src/extension/tool-description.ts +4 -2
- package/src/intercom/intercom-bridge.ts +1 -1
- package/src/intercom/native-supervisor-channel.ts +45 -6
- package/src/intercom/result-intercom.ts +7 -0
- package/src/runs/background/async-execution.ts +158 -17
- package/src/runs/background/async-job-tracker.ts +4 -0
- package/src/runs/background/async-resume.ts +47 -8
- package/src/runs/background/async-status.ts +94 -3
- package/src/runs/background/chain-append.ts +2 -0
- package/src/runs/background/completion-batcher.ts +6 -4
- package/src/runs/background/completion-dedupe.ts +2 -11
- package/src/runs/background/fleet-view.ts +9 -4
- package/src/runs/background/notify.ts +132 -120
- package/src/runs/background/process-terminal.ts +280 -0
- package/src/runs/background/result-watcher.ts +138 -78
- package/src/runs/background/run-status.ts +10 -2
- package/src/runs/background/scheduled-runs.ts +6 -1
- package/src/runs/background/stale-run-reconciler.ts +6 -0
- package/src/runs/background/subagent-runner.ts +406 -54
- package/src/runs/background/subagent-wait.ts +130 -4
- package/src/runs/background/wait-tool.ts +2 -2
- package/src/runs/foreground/chain-execution.ts +181 -111
- package/src/runs/foreground/execution.ts +147 -47
- package/src/runs/foreground/foreground-control.ts +90 -0
- package/src/runs/foreground/subagent-executor.ts +426 -165
- package/src/runs/shared/acceptance.ts +94 -41
- package/src/runs/shared/agent-contract.ts +38 -0
- package/src/runs/shared/capability-ceiling.ts +177 -0
- package/src/runs/shared/child-protocol.ts +1 -1
- package/src/runs/shared/completion-guard.ts +36 -5
- package/src/runs/shared/context-mode.ts +44 -0
- package/src/runs/shared/dynamic-fanout.ts +5 -5
- package/src/runs/shared/long-running-guard.ts +4 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +12 -6
- package/src/runs/shared/nested-events.ts +35 -3
- package/src/runs/shared/parallel-handoff.ts +154 -0
- package/src/runs/shared/parallel-utils.ts +11 -0
- package/src/runs/shared/pi-args.ts +148 -56
- package/src/runs/shared/run-history.ts +90 -5
- package/src/runs/shared/session-lease.ts +25 -5
- package/src/runs/shared/structured-output.ts +112 -7
- package/src/runs/shared/subagent-control.ts +4 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +31 -19
- package/src/runs/shared/task-intent.ts +10 -5
- package/src/runs/shared/tool-availability.ts +21 -3
- package/src/runs/shared/tool-budget.ts +11 -5
- package/src/runs/shared/turn-budget.ts +2 -1
- package/src/runs/shared/worktree.ts +63 -14
- package/src/shared/accessible-dir.ts +25 -0
- package/src/shared/artifacts.ts +37 -7
- package/src/shared/atomic-json.ts +14 -42
- package/src/shared/child-transcript.ts +52 -0
- package/src/shared/file-system-retry.ts +47 -0
- package/src/shared/launch-contract.ts +123 -0
- package/src/shared/settings.ts +9 -1
- package/src/shared/types.ts +314 -28
- package/src/shared/utils.ts +17 -42
- package/src/slash/delegation-adapters.ts +153 -6
- package/src/slash/delegation-json.ts +108 -0
- package/src/slash/delegation-request.ts +182 -36
- package/src/slash/prompt-template-bridge.ts +222 -37
- package/src/slash/selector.ts +147 -0
- package/src/slash/slash-commands.ts +15 -6
- package/src/slash/slash-live-state.ts +2 -2
- package/src/slash/subagents-admin.ts +42 -42
- package/src/tui/fleet-status.ts +362 -0
- package/src/tui/fleet-transcript.ts +472 -0
- package/src/tui/fleet.ts +318 -59
- package/src/tui/render.ts +17 -15
- package/src/watchdog/change-signature.ts +105 -12
- package/src/watchdog/review.ts +7 -2
- package/src/watchdog/runtime.ts +5 -3
- package/src/slash/subagents-editor.ts +0 -86
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { discoverAgents, discoverAgentsAll, type AgentConfig, type AgentScope, type AgentSource } from "../agents/agents.ts";
|
|
6
|
+
import { resolveExecutionAgentScope } from "../agents/agent-scope.ts";
|
|
7
|
+
import { buildSkillInjection, normalizeSkillInput, resolveSkillsWithFallback } from "../agents/skills.ts";
|
|
8
|
+
import { buildAgentMemoryInjection } from "../agents/agent-memory.ts";
|
|
9
|
+
import { buildModelCandidates, resolveEffectiveSubagentModel, type AvailableModelInfo, type ParentModel } from "../runs/shared/model-fallback.ts";
|
|
10
|
+
import { applyThinkingSuffix, resolvePiLaunchToolPlan, type PiLaunchToolPlan } from "../runs/shared/pi-args.ts";
|
|
11
|
+
import { injectOutputPathSystemPrompt, normalizeSingleOutputOverride, resolveSingleOutputPath } from "../runs/shared/single-output.ts";
|
|
12
|
+
import { getArtifactPaths, getArtifactsDir } from "../shared/artifacts.ts";
|
|
13
|
+
import { resolveEffectiveThinking } from "../shared/model-info.ts";
|
|
14
|
+
import { SUBAGENT_LIFECYCLE_ARTIFACT_VERSION, type ArtifactDirPreference, type ArtifactPaths, type JsonSchemaObject, type OutputMode } from "../shared/types.ts";
|
|
15
|
+
import type { ResolvedSubagentCapabilityCeiling, SubagentCapabilityAudit } from "../runs/shared/capability-ceiling.ts";
|
|
16
|
+
import { appendTurnBudgetSystemPrompt } from "../runs/shared/turn-budget.ts";
|
|
17
|
+
import type { ResolvedTurnBudget } from "../shared/types.ts";
|
|
18
|
+
import type { ResolvedMcpDirectToolSelection } from "../runs/shared/mcp-direct-tool-allowlist.ts";
|
|
19
|
+
import { resolveStepBehavior } from "../shared/settings.ts";
|
|
20
|
+
import { agentDefinitionDigest, AGENT_DEFINITION_PROJECTION_VERSION, launchBindingDigest } from "../shared/launch-contract.ts";
|
|
21
|
+
import { ASYNC_DIR, RESULTS_DIR, TEMP_ROOT_DIR } from "../shared/types.ts";
|
|
22
|
+
import { processTerminalCandidatePath, processTerminalPath } from "../runs/background/process-terminal.ts";
|
|
23
|
+
import { nestedResultsPath } from "../runs/shared/nested-events.ts";
|
|
24
|
+
|
|
25
|
+
export const SUBAGENT_LAUNCH_CONTRACT_VERSION = 2 as const;
|
|
26
|
+
|
|
27
|
+
export type SubagentLaunchContractReasonCode =
|
|
28
|
+
| "missing_agent"
|
|
29
|
+
| "ambiguous_agent"
|
|
30
|
+
| "missing_skill"
|
|
31
|
+
| "denied_required_tool"
|
|
32
|
+
| "invalid_artifact_dir"
|
|
33
|
+
| "invalid_cwd"
|
|
34
|
+
| "unsupported_mode";
|
|
35
|
+
|
|
36
|
+
export interface SubagentLaunchContractDiagnostic {
|
|
37
|
+
code: SubagentLaunchContractReasonCode | "host_required" | "snapshot_warning";
|
|
38
|
+
severity: "error" | "warning" | "host-required";
|
|
39
|
+
message: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface SubagentLaunchContractInput {
|
|
43
|
+
agent: string;
|
|
44
|
+
cwd: string;
|
|
45
|
+
task?: string;
|
|
46
|
+
agentScope?: AgentScope;
|
|
47
|
+
context?: "fresh" | "fork";
|
|
48
|
+
model?: string;
|
|
49
|
+
thinking?: string | false;
|
|
50
|
+
parentModel?: ParentModel;
|
|
51
|
+
availableModels?: ReadonlyArray<AvailableModelInfo | { provider: string; id: string; fullId?: string; reasoning?: boolean }>;
|
|
52
|
+
preferredProvider?: string;
|
|
53
|
+
skill?: string | string[] | boolean;
|
|
54
|
+
output?: string | boolean;
|
|
55
|
+
outputMode?: OutputMode;
|
|
56
|
+
outputSchema?: JsonSchemaObject;
|
|
57
|
+
turnBudget?: ResolvedTurnBudget;
|
|
58
|
+
artifacts?: boolean;
|
|
59
|
+
artifactDir?: ArtifactDirPreference;
|
|
60
|
+
parentSessionFile?: string | null;
|
|
61
|
+
sessionRoot?: string;
|
|
62
|
+
sessionDir?: string;
|
|
63
|
+
runId?: string;
|
|
64
|
+
/** Root run id supplied by a host when projecting nested async lifecycle paths. */
|
|
65
|
+
nestedRootRunId?: string;
|
|
66
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
67
|
+
inheritedCapabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface SubagentLaunchContractAgentCandidate {
|
|
71
|
+
name: string;
|
|
72
|
+
localName?: string;
|
|
73
|
+
packageName?: string;
|
|
74
|
+
source: AgentSource;
|
|
75
|
+
filePath: string;
|
|
76
|
+
disabled?: boolean;
|
|
77
|
+
selected: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface SubagentLaunchContractAgent {
|
|
81
|
+
name: string;
|
|
82
|
+
localName?: string;
|
|
83
|
+
packageName?: string;
|
|
84
|
+
source: AgentSource;
|
|
85
|
+
filePath: string;
|
|
86
|
+
definitionProjectionVersion: typeof AGENT_DEFINITION_PROJECTION_VERSION;
|
|
87
|
+
definitionDigest: string;
|
|
88
|
+
shadowedCandidates: SubagentLaunchContractAgentCandidate[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface SubagentLaunchContractSkills {
|
|
92
|
+
requested: string[];
|
|
93
|
+
resolved: Array<{ name: string; path: string; source: string }>;
|
|
94
|
+
missing: string[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface SubagentLaunchContractTools {
|
|
98
|
+
requestedBuiltin: string[];
|
|
99
|
+
declaredBuiltin: string[];
|
|
100
|
+
effectiveAllowlist: string[];
|
|
101
|
+
explicitAllowlist: boolean;
|
|
102
|
+
requiredChildTools: string[];
|
|
103
|
+
internalTools: string[];
|
|
104
|
+
mcp: ResolvedMcpDirectToolSelection[];
|
|
105
|
+
effectiveMcpTools: string[];
|
|
106
|
+
toolExtensionPaths: string[];
|
|
107
|
+
runtimeExtensions: string[];
|
|
108
|
+
configuredExtensions: string[];
|
|
109
|
+
extensionArgs: string[];
|
|
110
|
+
disableAmbientExtensions: boolean;
|
|
111
|
+
fanoutAuthorized: boolean;
|
|
112
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
113
|
+
capabilityAudit?: SubagentCapabilityAudit;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface SubagentLaunchContractRoots {
|
|
117
|
+
cwd: string;
|
|
118
|
+
sessionRoot?: string;
|
|
119
|
+
sessionDir?: string;
|
|
120
|
+
sessionFile?: string;
|
|
121
|
+
artifactsDir?: string;
|
|
122
|
+
artifactPaths?: ArtifactPaths;
|
|
123
|
+
outputPath?: string;
|
|
124
|
+
lifecycle?: {
|
|
125
|
+
asyncDir: string;
|
|
126
|
+
resultPath: string;
|
|
127
|
+
statusPath: string;
|
|
128
|
+
eventsPath: string;
|
|
129
|
+
processTerminalPath: string;
|
|
130
|
+
processTerminalCandidatePath: string;
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface SubagentLaunchContract {
|
|
135
|
+
version: typeof SUBAGENT_LAUNCH_CONTRACT_VERSION;
|
|
136
|
+
runId: string;
|
|
137
|
+
agent: SubagentLaunchContractAgent;
|
|
138
|
+
context: "fresh" | "fork";
|
|
139
|
+
model?: string;
|
|
140
|
+
modelCandidates: string[];
|
|
141
|
+
thinking?: string;
|
|
142
|
+
systemPromptMode: AgentConfig["systemPromptMode"];
|
|
143
|
+
inheritProjectContext: boolean;
|
|
144
|
+
inheritSkills: boolean;
|
|
145
|
+
skills: SubagentLaunchContractSkills;
|
|
146
|
+
tools: SubagentLaunchContractTools;
|
|
147
|
+
roots: SubagentLaunchContractRoots;
|
|
148
|
+
protocol: {
|
|
149
|
+
lifecycleArtifactVersion: number;
|
|
150
|
+
packageVersion: string;
|
|
151
|
+
};
|
|
152
|
+
diagnostics: SubagentLaunchContractDiagnostic[];
|
|
153
|
+
/** Digest of the resolved child inputs, recomputed by execution paths. */
|
|
154
|
+
launchContractDigest: string;
|
|
155
|
+
digest: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export type SubagentLaunchContractResult =
|
|
159
|
+
| { ok: true; contract: SubagentLaunchContract }
|
|
160
|
+
| { ok: false; code: SubagentLaunchContractReasonCode; message: string; diagnostics: SubagentLaunchContractDiagnostic[] };
|
|
161
|
+
|
|
162
|
+
function packageVersion(): string {
|
|
163
|
+
const packagePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "package.json");
|
|
164
|
+
const parsed = JSON.parse(fs.readFileSync(packagePath, "utf-8")) as { version?: unknown };
|
|
165
|
+
if (typeof parsed.version !== "string" || !parsed.version.trim()) {
|
|
166
|
+
throw new Error(`Invalid package version in '${packagePath}'.`);
|
|
167
|
+
}
|
|
168
|
+
return parsed.version;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function stableJson(value: unknown): string {
|
|
172
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
173
|
+
if (value && typeof value === "object") {
|
|
174
|
+
return `{${Object.entries(value as Record<string, unknown>)
|
|
175
|
+
.filter(([, entry]) => entry !== undefined)
|
|
176
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
177
|
+
.map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`)
|
|
178
|
+
.join(",")}}`;
|
|
179
|
+
}
|
|
180
|
+
return JSON.stringify(value);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function digestContract(contract: Omit<SubagentLaunchContract, "digest">): string {
|
|
184
|
+
return createHash("sha256").update(stableJson(contract)).digest("hex");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function normalizeAvailableModels(models: SubagentLaunchContractInput["availableModels"]): AvailableModelInfo[] {
|
|
188
|
+
return (models ?? []).map((model) => ({ ...model, fullId: model.fullId ?? `${model.provider}/${model.id}` }));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function candidateList(inputAgent: string, selected: AgentConfig | undefined, cwd: string): SubagentLaunchContractAgentCandidate[] {
|
|
192
|
+
const all = discoverAgentsAll(cwd);
|
|
193
|
+
return [...all.builtin, ...all.package, ...all.user, ...all.project]
|
|
194
|
+
.filter((agent) => agent.name === inputAgent || agent.localName === inputAgent)
|
|
195
|
+
.map((agent) => ({
|
|
196
|
+
name: agent.name,
|
|
197
|
+
...(agent.localName ? { localName: agent.localName } : {}),
|
|
198
|
+
...(agent.packageName ? { packageName: agent.packageName } : {}),
|
|
199
|
+
source: agent.source,
|
|
200
|
+
filePath: agent.filePath,
|
|
201
|
+
...(agent.disabled === true ? { disabled: true } : {}),
|
|
202
|
+
selected: Boolean(selected && agent.filePath === selected.filePath && agent.name === selected.name),
|
|
203
|
+
}));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export async function resolveSubagentLaunchContract(input: SubagentLaunchContractInput): Promise<SubagentLaunchContractResult> {
|
|
207
|
+
const diagnostics: SubagentLaunchContractDiagnostic[] = [];
|
|
208
|
+
const effectiveCwd = path.resolve(input.cwd);
|
|
209
|
+
try {
|
|
210
|
+
if (!fs.statSync(effectiveCwd).isDirectory()) {
|
|
211
|
+
return { ok: false, code: "invalid_cwd", message: `cwd '${effectiveCwd}' is not a directory.`, diagnostics };
|
|
212
|
+
}
|
|
213
|
+
} catch (error) {
|
|
214
|
+
const detail = error instanceof Error ? ` ${error.message}` : "";
|
|
215
|
+
return { ok: false, code: "invalid_cwd", message: `cwd '${effectiveCwd}' is not a directory.${detail}`, diagnostics };
|
|
216
|
+
}
|
|
217
|
+
if (input.context !== undefined && input.context !== "fresh" && input.context !== "fork") {
|
|
218
|
+
return { ok: false, code: "unsupported_mode", message: `Unsupported context '${String(input.context)}'; expected 'fresh' or 'fork'.`, diagnostics };
|
|
219
|
+
}
|
|
220
|
+
if (input.artifactDir !== undefined && input.artifactDir !== "project" && input.artifactDir !== "session" && input.artifactDir !== "temp") {
|
|
221
|
+
return { ok: false, code: "invalid_artifact_dir", message: `Unsupported artifactDir '${String(input.artifactDir)}'; expected 'project', 'session', or 'temp'.`, diagnostics };
|
|
222
|
+
}
|
|
223
|
+
if (input.context === "fork") {
|
|
224
|
+
diagnostics.push({ code: "host_required", severity: "host-required", message: "Exact fork session branching and fork-thinking downgrade checks require Pi host session and model-registry snapshots." });
|
|
225
|
+
}
|
|
226
|
+
const scope = resolveExecutionAgentScope(input.agentScope);
|
|
227
|
+
const discovered = discoverAgents(effectiveCwd, scope);
|
|
228
|
+
const matches = discovered.agents.filter((agent) => agent.name === input.agent || agent.localName === input.agent);
|
|
229
|
+
if (matches.length === 0) {
|
|
230
|
+
return { ok: false, code: "missing_agent", message: `Unknown agent: ${input.agent}`, diagnostics };
|
|
231
|
+
}
|
|
232
|
+
if (matches.length > 1) {
|
|
233
|
+
return { ok: false, code: "ambiguous_agent", message: `Ambiguous agent: ${input.agent}`, diagnostics };
|
|
234
|
+
}
|
|
235
|
+
const agent = matches[0]!;
|
|
236
|
+
const runId = input.runId ?? "preflight";
|
|
237
|
+
const skillInput = normalizeSkillInput(input.skill);
|
|
238
|
+
const outputOverride = normalizeSingleOutputOverride(input.output, agent.output);
|
|
239
|
+
const behavior = resolveStepBehavior(agent, {
|
|
240
|
+
...(outputOverride !== undefined ? { output: outputOverride } : {}),
|
|
241
|
+
...(input.outputMode !== undefined ? { outputMode: input.outputMode } : {}),
|
|
242
|
+
...(skillInput !== undefined ? { skills: skillInput } : {}),
|
|
243
|
+
...(input.model !== undefined ? { model: input.model } : {}),
|
|
244
|
+
});
|
|
245
|
+
const requestedSkills = behavior.skills === false ? [] : behavior.skills;
|
|
246
|
+
const resolvedSkills = resolveSkillsWithFallback(
|
|
247
|
+
requestedSkills,
|
|
248
|
+
effectiveCwd,
|
|
249
|
+
effectiveCwd,
|
|
250
|
+
agent.skillPath,
|
|
251
|
+
agent.filePath ? path.dirname(agent.filePath) : effectiveCwd,
|
|
252
|
+
);
|
|
253
|
+
if (resolvedSkills.missing.includes("pi-subagents")) {
|
|
254
|
+
return { ok: false, code: "missing_skill", message: "The pi-subagents orchestration skill is not child-injectable.", diagnostics };
|
|
255
|
+
}
|
|
256
|
+
if (resolvedSkills.missing.length > 0) diagnostics.push({ code: "missing_skill", severity: "error", message: `Missing skills: ${resolvedSkills.missing.join(", ")}` });
|
|
257
|
+
|
|
258
|
+
const availableModels = normalizeAvailableModels(input.availableModels);
|
|
259
|
+
const preferredProvider = input.preferredProvider ?? input.parentModel?.provider;
|
|
260
|
+
const primaryModel = resolveEffectiveSubagentModel(input.model, agent.model, input.parentModel, availableModels, preferredProvider, { scope: discovered.modelScope });
|
|
261
|
+
const effectiveThinkingConfig = input.thinking !== undefined ? input.thinking : agent.thinking;
|
|
262
|
+
const model = applyThinkingSuffix(primaryModel, effectiveThinkingConfig, input.thinking !== undefined);
|
|
263
|
+
const modelCandidates = buildModelCandidates(primaryModel, agent.fallbackModels, availableModels, preferredProvider, { scope: discovered.modelScope })
|
|
264
|
+
.map((candidate) => applyThinkingSuffix(candidate, effectiveThinkingConfig, input.thinking !== undefined) ?? candidate);
|
|
265
|
+
let toolPlan: PiLaunchToolPlan;
|
|
266
|
+
try {
|
|
267
|
+
toolPlan = resolvePiLaunchToolPlan({
|
|
268
|
+
tools: agent.tools,
|
|
269
|
+
extensions: agent.extensions,
|
|
270
|
+
subagentOnlyExtensions: agent.subagentOnlyExtensions,
|
|
271
|
+
mcpDirectTools: agent.mcpDirectTools,
|
|
272
|
+
cwd: effectiveCwd,
|
|
273
|
+
requireReadTool: resolvedSkills.resolved.length > 0,
|
|
274
|
+
structuredOutput: Boolean(input.outputSchema),
|
|
275
|
+
capabilityCeiling: input.capabilityCeiling,
|
|
276
|
+
inheritedCapabilityCeiling: input.inheritedCapabilityCeiling,
|
|
277
|
+
});
|
|
278
|
+
} catch (error) {
|
|
279
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
280
|
+
diagnostics.push({ code: "denied_required_tool", severity: "error", message });
|
|
281
|
+
return { ok: false, code: "denied_required_tool", message, diagnostics };
|
|
282
|
+
}
|
|
283
|
+
const artifactsEnabled = input.artifacts !== false;
|
|
284
|
+
const artifactsDir = artifactsEnabled ? getArtifactsDir(input.parentSessionFile ?? null, effectiveCwd, input.artifactDir ?? "project") : undefined;
|
|
285
|
+
const artifactPaths = artifactsDir ? getArtifactPaths(artifactsDir, runId, agent.name, 0) : undefined;
|
|
286
|
+
const outputPath = resolveSingleOutputPath(behavior.output, effectiveCwd, effectiveCwd, artifactsDir ? path.join(artifactsDir, "outputs", runId) : undefined);
|
|
287
|
+
const sessionRoot = input.sessionDir ? path.resolve(input.sessionDir) : input.sessionRoot ? path.join(path.resolve(input.sessionRoot), runId) : undefined;
|
|
288
|
+
const sessionDir = sessionRoot ? path.join(sessionRoot, "run-0") : undefined;
|
|
289
|
+
const lifecycleAsyncDir = input.nestedRootRunId
|
|
290
|
+
? path.join(TEMP_ROOT_DIR, "nested-subagent-runs", input.nestedRootRunId, runId)
|
|
291
|
+
: path.join(ASYNC_DIR, runId);
|
|
292
|
+
const lifecycleResultPath = input.nestedRootRunId
|
|
293
|
+
? nestedResultsPath(input.nestedRootRunId, runId)
|
|
294
|
+
: path.join(RESULTS_DIR, `${runId}.json`);
|
|
295
|
+
if (!sessionDir) diagnostics.push({ code: "host_required", severity: "host-required", message: "No sessionRoot/sessionDir was supplied; exact child session paths require the Pi host session-root policy." });
|
|
296
|
+
if (input.availableModels === undefined && (input.model || agent.model || input.parentModel)) {
|
|
297
|
+
diagnostics.push({ code: "host_required", severity: "host-required", message: "No availableModels snapshot was supplied; model resolution may differ from the active Pi host registry." });
|
|
298
|
+
}
|
|
299
|
+
if (resolvedSkills.missing.length > 0) {
|
|
300
|
+
return { ok: false, code: "missing_skill", message: `Missing skills: ${resolvedSkills.missing.join(", ")}`, diagnostics };
|
|
301
|
+
}
|
|
302
|
+
let effectiveSystemPrompt = agent.systemPrompt?.trim() ?? "";
|
|
303
|
+
if (resolvedSkills.resolved.length > 0) {
|
|
304
|
+
const skillInjection = buildSkillInjection(resolvedSkills.resolved);
|
|
305
|
+
effectiveSystemPrompt = effectiveSystemPrompt ? `${effectiveSystemPrompt}\n\n${skillInjection}` : skillInjection;
|
|
306
|
+
}
|
|
307
|
+
const memoryInjection = buildAgentMemoryInjection(agent, effectiveCwd);
|
|
308
|
+
if (memoryInjection) effectiveSystemPrompt = effectiveSystemPrompt ? `${effectiveSystemPrompt}\n\n${memoryInjection}` : memoryInjection;
|
|
309
|
+
effectiveSystemPrompt = injectOutputPathSystemPrompt(effectiveSystemPrompt, outputPath, agent);
|
|
310
|
+
const turnBudget = input.turnBudget ?? agent.defaultTurnBudget;
|
|
311
|
+
effectiveSystemPrompt = appendTurnBudgetSystemPrompt(effectiveSystemPrompt, turnBudget);
|
|
312
|
+
const candidates = candidateList(input.agent, agent, effectiveCwd);
|
|
313
|
+
const shadowedCandidates = candidates.filter((candidate) => !candidate.selected);
|
|
314
|
+
const definitionDigest = agentDefinitionDigest(agent);
|
|
315
|
+
const contractBase: Omit<SubagentLaunchContract, "digest"> = {
|
|
316
|
+
version: SUBAGENT_LAUNCH_CONTRACT_VERSION,
|
|
317
|
+
runId,
|
|
318
|
+
agent: {
|
|
319
|
+
name: agent.name,
|
|
320
|
+
...(agent.localName ? { localName: agent.localName } : {}),
|
|
321
|
+
...(agent.packageName ? { packageName: agent.packageName } : {}),
|
|
322
|
+
source: agent.source,
|
|
323
|
+
filePath: agent.filePath,
|
|
324
|
+
definitionProjectionVersion: AGENT_DEFINITION_PROJECTION_VERSION,
|
|
325
|
+
definitionDigest,
|
|
326
|
+
shadowedCandidates,
|
|
327
|
+
},
|
|
328
|
+
context: input.context ?? agent.defaultContext ?? "fresh",
|
|
329
|
+
...(model ? { model } : {}),
|
|
330
|
+
modelCandidates,
|
|
331
|
+
...(resolveEffectiveThinking(model, effectiveThinkingConfig) ? { thinking: resolveEffectiveThinking(model, effectiveThinkingConfig) } : {}),
|
|
332
|
+
systemPromptMode: agent.systemPromptMode,
|
|
333
|
+
inheritProjectContext: agent.inheritProjectContext,
|
|
334
|
+
inheritSkills: agent.inheritSkills,
|
|
335
|
+
skills: {
|
|
336
|
+
requested: requestedSkills,
|
|
337
|
+
resolved: resolvedSkills.resolved.map((skill) => ({ name: skill.name, path: skill.path, source: skill.source })),
|
|
338
|
+
missing: resolvedSkills.missing,
|
|
339
|
+
},
|
|
340
|
+
tools: {
|
|
341
|
+
requestedBuiltin: toolPlan.requestedBuiltinTools,
|
|
342
|
+
declaredBuiltin: toolPlan.declaredBuiltinTools,
|
|
343
|
+
effectiveAllowlist: toolPlan.effectiveToolAllowlist,
|
|
344
|
+
explicitAllowlist: toolPlan.explicitToolAllowlist,
|
|
345
|
+
requiredChildTools: toolPlan.requiredChildTools,
|
|
346
|
+
internalTools: toolPlan.internalTools,
|
|
347
|
+
mcp: toolPlan.effectiveMcpSelections,
|
|
348
|
+
effectiveMcpTools: toolPlan.effectiveMcpTools,
|
|
349
|
+
toolExtensionPaths: toolPlan.toolExtensionPaths,
|
|
350
|
+
runtimeExtensions: toolPlan.runtimeExtensions,
|
|
351
|
+
configuredExtensions: toolPlan.configuredExtensions,
|
|
352
|
+
extensionArgs: toolPlan.extensionArgs,
|
|
353
|
+
disableAmbientExtensions: toolPlan.disableAmbientExtensions,
|
|
354
|
+
fanoutAuthorized: toolPlan.fanoutAuthorized,
|
|
355
|
+
...(toolPlan.capabilityCeiling ? { capabilityCeiling: toolPlan.capabilityCeiling } : {}),
|
|
356
|
+
...(toolPlan.capabilityAudit ? { capabilityAudit: toolPlan.capabilityAudit } : {}),
|
|
357
|
+
},
|
|
358
|
+
roots: {
|
|
359
|
+
cwd: effectiveCwd,
|
|
360
|
+
...(sessionRoot ? { sessionRoot } : {}),
|
|
361
|
+
...(sessionDir ? { sessionDir, sessionFile: path.join(sessionDir, "session.jsonl") } : {}),
|
|
362
|
+
...(artifactsDir ? { artifactsDir } : {}),
|
|
363
|
+
...(artifactPaths ? { artifactPaths } : {}),
|
|
364
|
+
...(outputPath ? { outputPath } : {}),
|
|
365
|
+
lifecycle: {
|
|
366
|
+
asyncDir: lifecycleAsyncDir,
|
|
367
|
+
resultPath: lifecycleResultPath,
|
|
368
|
+
statusPath: path.join(lifecycleAsyncDir, "status.json"),
|
|
369
|
+
eventsPath: path.join(lifecycleAsyncDir, "events.jsonl"),
|
|
370
|
+
processTerminalPath: processTerminalPath(lifecycleAsyncDir),
|
|
371
|
+
processTerminalCandidatePath: processTerminalCandidatePath(lifecycleAsyncDir),
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
protocol: {
|
|
375
|
+
lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
376
|
+
packageVersion: packageVersion(),
|
|
377
|
+
},
|
|
378
|
+
diagnostics,
|
|
379
|
+
launchContractDigest: launchBindingDigest({
|
|
380
|
+
task: input.task ?? "",
|
|
381
|
+
definitionDigest,
|
|
382
|
+
...(model ? { model } : {}),
|
|
383
|
+
modelCandidates,
|
|
384
|
+
...(resolveEffectiveThinking(model, effectiveThinkingConfig) ? { thinking: resolveEffectiveThinking(model, effectiveThinkingConfig) } : {}),
|
|
385
|
+
systemPrompt: effectiveSystemPrompt,
|
|
386
|
+
systemPromptMode: agent.systemPromptMode,
|
|
387
|
+
inheritProjectContext: agent.inheritProjectContext,
|
|
388
|
+
inheritSkills: agent.inheritSkills,
|
|
389
|
+
skills: requestedSkills,
|
|
390
|
+
tools: toolPlan.effectiveToolAllowlist,
|
|
391
|
+
extensions: toolPlan.extensionArgs,
|
|
392
|
+
mcpDirectTools: toolPlan.effectiveMcpTools,
|
|
393
|
+
...(outputPath ? { outputPath } : {}),
|
|
394
|
+
outputMode: input.outputMode ?? "inline",
|
|
395
|
+
...(input.outputSchema ? { structuredOutputSchema: input.outputSchema } : {}),
|
|
396
|
+
}),
|
|
397
|
+
};
|
|
398
|
+
return { ok: true, contract: { ...contractBase, digest: digestContract(contractBase) } };
|
|
399
|
+
}
|
package/src/extension/config.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import type { ExtensionConfig } from "../shared/types.ts";
|
|
3
|
+
import type { ArtifactDirPreference, ExtensionConfig } from "../shared/types.ts";
|
|
4
4
|
import { getAgentDir } from "../shared/utils.ts";
|
|
5
5
|
|
|
6
|
+
const ARTIFACT_DIR_PREFERENCES = new Set<ArtifactDirPreference>(["project", "session", "temp"]);
|
|
7
|
+
|
|
6
8
|
export function getConfigPath(): string {
|
|
7
9
|
return path.join(getAgentDir(), "extensions", "subagent", "config.json");
|
|
8
10
|
}
|
|
@@ -13,6 +15,10 @@ function readConfigForUpdate(configPath = getConfigPath()): ExtensionConfig {
|
|
|
13
15
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
14
16
|
throw new Error(`Subagent config at '${configPath}' must be a JSON object`);
|
|
15
17
|
}
|
|
18
|
+
const config = parsed as Record<string, unknown>;
|
|
19
|
+
if (config.artifactDir !== undefined && !ARTIFACT_DIR_PREFERENCES.has(config.artifactDir as ArtifactDirPreference)) {
|
|
20
|
+
throw new Error(`config.artifactDir must be "project", "session", or "temp"`);
|
|
21
|
+
}
|
|
16
22
|
return parsed as ExtensionConfig;
|
|
17
23
|
}
|
|
18
24
|
|
package/src/extension/index.ts
CHANGED
|
@@ -20,10 +20,13 @@ 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
22
|
import { discoverAgents } from "../agents/agents.ts";
|
|
23
|
+
import { ensureAccessibleDir } from "../shared/accessible-dir.ts";
|
|
23
24
|
import { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from "../shared/artifacts.ts";
|
|
24
25
|
import { resolveCurrentSessionId } from "../shared/session-identity.ts";
|
|
25
26
|
import { cleanupOldChainDirs } from "../shared/settings.ts";
|
|
26
27
|
import { clearLegacyResultAnimationTimer, renderSubagentResult } from "../tui/render.ts";
|
|
28
|
+
import { openSubagentFleet } from "../tui/fleet.ts";
|
|
29
|
+
import { SubagentFleetStatus } from "../tui/fleet-status.ts";
|
|
27
30
|
import { SubagentParams } from "./schemas.ts";
|
|
28
31
|
import { validateChainInput } from "./chain-validation.ts";
|
|
29
32
|
import { createSubagentExecutor, type SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
@@ -44,6 +47,7 @@ import { drainOutstandingWork } from "../runs/background/auto-drain.ts";
|
|
|
44
47
|
import registerSubagentNotify, { parseSubagentNotifyContent, type SubagentNotifyDetails } from "../runs/background/notify.ts";
|
|
45
48
|
import { formatSteeringNotice, handleSubagentSteeringNotice, SUBAGENT_STEERING_MESSAGE_TYPE, type SubagentSteeringMessageDetails } from "./steering-notices.ts";
|
|
46
49
|
import { SUBAGENT_CHILD_ENV, SUBAGENT_PARENT_SESSION_ENV } from "../runs/shared/pi-args.ts";
|
|
50
|
+
import { resolveCurrentSubagentCapabilityCeiling } from "../runs/shared/capability-ceiling.ts";
|
|
47
51
|
import { formatDuration, shortenPath } from "../shared/formatters.ts";
|
|
48
52
|
import { loadConfig } from "./config.ts";
|
|
49
53
|
import { buildSubagentToolDescription } from "./tool-description.ts";
|
|
@@ -92,28 +96,6 @@ function expandTilde(p: string): string {
|
|
|
92
96
|
return p.startsWith("~/") ? path.join(os.homedir(), p.slice(2)) : p;
|
|
93
97
|
}
|
|
94
98
|
|
|
95
|
-
/**
|
|
96
|
-
* Create a directory and verify it is actually accessible.
|
|
97
|
-
* On Windows with Azure AD/Entra ID, directories created shortly after
|
|
98
|
-
* wake-from-sleep can end up with broken NTFS ACLs (null DACL) when the
|
|
99
|
-
* cloud SID cannot be resolved without network connectivity. This leaves
|
|
100
|
-
* the directory completely inaccessible to the creating user.
|
|
101
|
-
*/
|
|
102
|
-
function ensureAccessibleDir(dirPath: string): void {
|
|
103
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
104
|
-
try {
|
|
105
|
-
fs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);
|
|
106
|
-
} catch {
|
|
107
|
-
try {
|
|
108
|
-
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
109
|
-
} catch {
|
|
110
|
-
// Best effort: retry mkdir/access even if cleanup fails.
|
|
111
|
-
}
|
|
112
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
113
|
-
fs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
99
|
function isSlashResultRunning(result: { details?: Details }): boolean {
|
|
118
100
|
return result.details?.progress?.some((entry) => entry.status === "running")
|
|
119
101
|
|| result.details?.results.some((entry) => entry.progress?.status === "running")
|
|
@@ -210,12 +192,16 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
210
192
|
const config = loadConfig();
|
|
211
193
|
const waitToolConfig = resolveWaitToolConfig(config.waitTool);
|
|
212
194
|
const asyncByDefault = config.asyncByDefault === true;
|
|
195
|
+
const fleetViewEnabled = config.fleetView !== false;
|
|
196
|
+
const asyncWidgetEnabled = config.asyncWidget === true || (!fleetViewEnabled && config.asyncWidget !== false);
|
|
213
197
|
const tempArtifactsDir = getArtifactsDir(null);
|
|
214
198
|
cleanupAllArtifactDirs(DEFAULT_ARTIFACT_CONFIG.cleanupDays);
|
|
215
199
|
|
|
216
200
|
const state: SubagentState = {
|
|
217
201
|
baseCwd: "",
|
|
218
202
|
currentSessionId: null,
|
|
203
|
+
artifactDirPreference: config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir,
|
|
204
|
+
parentSessionFile: null,
|
|
219
205
|
subagentInProgress: false,
|
|
220
206
|
subagentSpawns: {
|
|
221
207
|
sessionId: null,
|
|
@@ -244,22 +230,30 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
244
230
|
|
|
245
231
|
const supervisorChannel = createNativeSupervisorChannel(pi, state);
|
|
246
232
|
const mainWatchdog = registerMainWatchdog(pi);
|
|
247
|
-
|
|
233
|
+
const completionNotifier = registerSubagentNotify(pi, state, { batchConfig: config.completionBatch });
|
|
234
|
+
const fleetStatus = fleetViewEnabled
|
|
235
|
+
? new SubagentFleetStatus(state, async (itemKey) => {
|
|
236
|
+
const ctx = state.lastUiContext;
|
|
237
|
+
if (!ctx?.hasUI) return;
|
|
238
|
+
await openSubagentFleet(ctx, state, { initialKey: itemKey });
|
|
239
|
+
})
|
|
240
|
+
: undefined;
|
|
248
241
|
const { startResultWatcher, primeExistingResults, stopResultWatcher } = createResultWatcher(
|
|
249
242
|
pi,
|
|
250
243
|
state,
|
|
251
244
|
RESULTS_DIR,
|
|
252
245
|
10 * 60 * 1000,
|
|
246
|
+
{ notifier: completionNotifier },
|
|
253
247
|
);
|
|
254
|
-
startResultWatcher();
|
|
255
|
-
primeExistingResults();
|
|
256
248
|
|
|
257
249
|
const runtimeCleanup = () => {
|
|
258
|
-
disposeSubagentNotify();
|
|
259
|
-
mainWatchdog.dispose();
|
|
260
250
|
stopResultWatcher();
|
|
251
|
+
state.currentSessionId = null;
|
|
252
|
+
completionNotifier.dispose();
|
|
253
|
+
mainWatchdog.dispose();
|
|
261
254
|
scheduledRunManager.stop();
|
|
262
255
|
supervisorChannel.dispose();
|
|
256
|
+
fleetStatus?.dispose();
|
|
263
257
|
clearPendingForegroundControlNotices(state);
|
|
264
258
|
if (state.poller) {
|
|
265
259
|
clearInterval(state.poller);
|
|
@@ -269,7 +263,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
269
263
|
globalStore[runtimeCleanupStoreKey] = runtimeCleanup;
|
|
270
264
|
|
|
271
265
|
const { ensurePoller, refreshWidget, handleStarted, handleComplete, resetJobs, restoreActiveJobs } = createAsyncJobTracker(pi, state, ASYNC_DIR, {
|
|
272
|
-
widgetEnabled:
|
|
266
|
+
widgetEnabled: asyncWidgetEnabled,
|
|
273
267
|
});
|
|
274
268
|
let executorExecute: ((id: string, params: SubagentParamsLike, signal: AbortSignal, onUpdate: ((r: AgentToolResult<Details>) => void) | undefined, ctx: ExtensionContext) => Promise<AgentToolResult<Details>>) | undefined;
|
|
275
269
|
const scheduledRunManager = createScheduledRunManager({
|
|
@@ -284,6 +278,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
284
278
|
}
|
|
285
279
|
return executorExecute(randomUUID(), params, signal, undefined, ctx);
|
|
286
280
|
},
|
|
281
|
+
resolveCapabilityCeiling: (sessionId) => resolveCurrentSubagentCapabilityCeiling(sessionId),
|
|
287
282
|
});
|
|
288
283
|
const executor = createSubagentExecutor({
|
|
289
284
|
pi,
|
|
@@ -377,6 +372,10 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
377
372
|
getContext: () => state.lastUiContext,
|
|
378
373
|
execute: (requestId, params, signal, ctx, onUpdate) =>
|
|
379
374
|
executeSubagentCollapsed(requestId, params, signal, onUpdate, ctx),
|
|
375
|
+
executeVersioned: (requestId, params, signal, ctx, onUpdate) => {
|
|
376
|
+
if (ctx.hasUI) ctx.ui.setToolsExpanded(false);
|
|
377
|
+
return executor.executeDelegated(requestId, params, signal, onUpdate, ctx);
|
|
378
|
+
},
|
|
380
379
|
});
|
|
381
380
|
|
|
382
381
|
const rpcBridge = registerSubagentRpcBridge({
|
|
@@ -472,8 +471,6 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
472
471
|
}
|
|
473
472
|
}
|
|
474
473
|
}
|
|
475
|
-
disposeSubagentNotify = registerSubagentNotify(pi, state, { batchConfig: config.completionBatch });
|
|
476
|
-
|
|
477
474
|
const existingVisibleControlNotices = globalStore[controlNoticeSeenStoreKey];
|
|
478
475
|
const visibleControlNotices = existingVisibleControlNotices instanceof Set ? existingVisibleControlNotices as Set<string> : new Set<string>();
|
|
479
476
|
globalStore[controlNoticeSeenStoreKey] = visibleControlNotices;
|
|
@@ -488,9 +485,17 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
488
485
|
const steeringNoticeHandler = (payload: unknown) => {
|
|
489
486
|
handleSubagentSteeringNotice({ pi, state, details: payload as SubagentSteeringMessageDetails });
|
|
490
487
|
};
|
|
488
|
+
const asyncStartedHandler = (payload: unknown) => {
|
|
489
|
+
handleStarted(payload);
|
|
490
|
+
fleetStatus?.refresh();
|
|
491
|
+
};
|
|
492
|
+
const asyncCompleteHandler = (payload: unknown) => {
|
|
493
|
+
handleComplete(payload);
|
|
494
|
+
fleetStatus?.refresh();
|
|
495
|
+
};
|
|
491
496
|
const eventUnsubscribes = [
|
|
492
|
-
pi.events.on(SUBAGENT_ASYNC_STARTED_EVENT,
|
|
493
|
-
pi.events.on(SUBAGENT_ASYNC_COMPLETE_EVENT,
|
|
497
|
+
pi.events.on(SUBAGENT_ASYNC_STARTED_EVENT, asyncStartedHandler),
|
|
498
|
+
pi.events.on(SUBAGENT_ASYNC_COMPLETE_EVENT, asyncCompleteHandler),
|
|
494
499
|
pi.events.on(SUBAGENT_CONTROL_EVENT, controlEventHandler),
|
|
495
500
|
pi.events.on(SUBAGENT_STEERING_NOTICE_EVENT, steeringNoticeHandler),
|
|
496
501
|
rpcBridge.dispose,
|
|
@@ -501,6 +506,8 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
501
506
|
if (event.toolName !== "subagent") return;
|
|
502
507
|
if (!ctx.hasUI) return;
|
|
503
508
|
state.lastUiContext = ctx;
|
|
509
|
+
fleetStatus?.setContext(ctx);
|
|
510
|
+
fleetStatus?.refresh();
|
|
504
511
|
if (state.asyncJobs.size > 0) {
|
|
505
512
|
refreshWidget(ctx);
|
|
506
513
|
ensurePoller();
|
|
@@ -518,9 +525,10 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
518
525
|
}
|
|
519
526
|
};
|
|
520
527
|
|
|
521
|
-
const resetSessionState = (ctx: ExtensionContext) => {
|
|
528
|
+
const resetSessionState = (ctx: ExtensionContext, recovering: boolean) => {
|
|
522
529
|
state.baseCwd = ctx.cwd;
|
|
523
530
|
state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
531
|
+
state.parentSessionFile = ctx.sessionManager.getSessionFile();
|
|
524
532
|
state.subagentSpawns = {
|
|
525
533
|
sessionId: state.currentSessionId,
|
|
526
534
|
count: 0,
|
|
@@ -546,17 +554,23 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
546
554
|
restoreActiveJobs(ctx);
|
|
547
555
|
scheduledRunManager.bindSession(ctx);
|
|
548
556
|
restoreSlashFinalSnapshots(ctx.sessionManager.getEntries());
|
|
549
|
-
|
|
557
|
+
startResultWatcher();
|
|
558
|
+
primeExistingResults({ triggerTurn: !recovering });
|
|
559
|
+
fleetStatus?.setContext(ctx);
|
|
550
560
|
};
|
|
551
561
|
|
|
552
|
-
pi.on("session_start", (
|
|
553
|
-
|
|
562
|
+
pi.on("session_start", (event, ctx) => {
|
|
563
|
+
const recovering = event.reason === "startup" || event.reason === "reload" || event.reason === "resume";
|
|
564
|
+
resetSessionState(ctx, recovering);
|
|
554
565
|
rpcBridge.emitReady(ctx);
|
|
555
566
|
supervisorChannel.start();
|
|
556
567
|
});
|
|
557
568
|
|
|
558
569
|
pi.on("session_shutdown", () => {
|
|
559
|
-
|
|
570
|
+
stopResultWatcher();
|
|
571
|
+
state.currentSessionId = null;
|
|
572
|
+
state.parentSessionFile = null;
|
|
573
|
+
completionNotifier.dispose();
|
|
560
574
|
delete process.env[SUBAGENT_PARENT_SESSION_ENV];
|
|
561
575
|
for (const unsubscribe of eventUnsubscribes) {
|
|
562
576
|
try {
|
|
@@ -568,7 +582,6 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
568
582
|
if (globalStore[eventUnsubscribeStoreKey] === eventUnsubscribes) {
|
|
569
583
|
delete globalStore[eventUnsubscribeStoreKey];
|
|
570
584
|
}
|
|
571
|
-
stopResultWatcher();
|
|
572
585
|
scheduledRunManager.stop();
|
|
573
586
|
if (state.poller) clearInterval(state.poller);
|
|
574
587
|
state.poller = null;
|
|
@@ -584,6 +597,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
584
597
|
promptTemplateBridge.cancelAll();
|
|
585
598
|
promptTemplateBridge.dispose();
|
|
586
599
|
supervisorChannel.dispose();
|
|
600
|
+
fleetStatus?.dispose();
|
|
587
601
|
if (globalStore[runtimeCleanupStoreKey] === runtimeCleanup) {
|
|
588
602
|
delete globalStore[runtimeCleanupStoreKey];
|
|
589
603
|
}
|