pi-subagents 0.61.0 → 0.62.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -1
- package/docs/agents.md +6 -2
- package/docs/configuration.md +3 -3
- package/docs/extension-api.md +2 -2
- package/docs/observability.md +1 -1
- package/docs/tool-reference.md +2 -2
- package/install.mjs +0 -1
- package/package.json +1 -1
- package/skills/pi-subagents/references/execution-controls.md +3 -4
- package/src/agents/agent-management.ts +19 -0
- package/src/agents/agent-serializer.ts +3 -0
- package/src/agents/agents.ts +16 -2
- package/src/agents/runtime-agent-registry.ts +5 -1
- package/src/api/preflight.ts +4 -0
- package/src/extension/public-execution.ts +1 -0
- package/src/extension/schemas.ts +6 -2
- package/src/extension/tool-description.ts +1 -1
- package/src/runs/background/active-async-capacity.ts +26 -7
- package/src/runs/background/async-execution.ts +47 -16
- package/src/runs/background/async-resume.ts +3 -2
- package/src/runs/background/process-terminal.ts +16 -0
- package/src/runs/background/scheduled-runs.ts +63 -6
- package/src/runs/background/steering.ts +4 -1
- package/src/runs/background/subagent-runner.ts +14 -7
- package/src/runs/background/wait-tool.ts +1 -7
- package/src/runs/foreground/execution.ts +14 -7
- package/src/runs/foreground/subagent-executor.ts +3 -2
- package/src/runs/shared/acceptance.ts +70 -9
- package/src/runs/shared/capability-ceiling.ts +1 -0
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/parallel-utils.ts +2 -6
- package/src/runs/shared/permissions.ts +1 -1
- package/src/runs/shared/pi-args.ts +24 -12
- package/src/runs/shared/pi-spawn.ts +69 -35
- package/src/runs/shared/structured-output.ts +33 -6
- package/src/runs/shared/subagent-prompt-runtime.ts +20 -3
- package/src/runs/shared/task-intent.ts +17 -6
- package/src/runs/shared/tool-timeout.ts +1 -1
- package/src/shared/atomic-json.ts +3 -1
- package/src/shared/fork-context.ts +0 -12
- package/src/shared/fork-session-cwd.ts +27 -0
- package/src/shared/launch-contract.ts +3 -0
- package/src/shared/types.ts +3 -1
- package/src/slash/slash-commands.ts +1 -1
- package/src/slash/subagents-admin.ts +2 -0
|
@@ -11,7 +11,7 @@ import { createRequire } from "node:module";
|
|
|
11
11
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
12
12
|
import { discoverAgents, formatUnknownAgentError, unknownAgentDiagnosticContext, type AgentConfig, type UnknownAgentDiagnosticContext } from "../../agents/agents.ts";
|
|
13
13
|
import { appendAgentRefinementOverlay } from "../../agents/agent-refinements.ts";
|
|
14
|
-
import { writePrivateAtomicJson } from "../../shared/atomic-json.ts";
|
|
14
|
+
import { createAtomicJsonWriter, writePrivateAtomicJson } from "../../shared/atomic-json.ts";
|
|
15
15
|
import { currentCompletionOwnerId } from "../../shared/completion-owner.ts";
|
|
16
16
|
import { planChildLaunch, resolveStepBehavior, suppressProgressForReadOnlyTask, type ResolvedStepBehavior } from "../shared/child-launch-plan.ts";
|
|
17
17
|
import { applyThinkingSuffix, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan } from "../shared/pi-args.ts";
|
|
@@ -19,7 +19,7 @@ import { injectOutputPathSystemPrompt, injectSingleOutputInstruction, normalizeS
|
|
|
19
19
|
import { buildChainInstructions, isDynamicParallelStep, isParallelStep, resolveExistingReadPaths, writeInitialProgressFile, type ChainStep, type SequentialStep, type StepOverrides } from "../../shared/settings.ts";
|
|
20
20
|
import type { RunnerStep } from "../shared/parallel-utils.ts";
|
|
21
21
|
import type { ContextMode } from "../shared/context-mode.ts";
|
|
22
|
-
import { resolvePiPackageRoot } from "../shared/pi-spawn.ts";
|
|
22
|
+
import { resolveInstalledPiPackageRoot, resolvePiPackageRoot } from "../shared/pi-spawn.ts";
|
|
23
23
|
import { preflightLaunchCwd } from "../shared/launch-cwd.ts";
|
|
24
24
|
import { resolveNodeExecutable } from "../../shared/node-executable.ts";
|
|
25
25
|
import { backgroundProcessOptions } from "../shared/background-process-options.ts";
|
|
@@ -35,7 +35,7 @@ import { resolveExpectedWorktreeAgentCwd } from "../shared/worktree.ts";
|
|
|
35
35
|
import { buildWorkflowGraphSnapshot } from "../shared/workflow-graph.ts";
|
|
36
36
|
import { ChainOutputValidationError, validateChainOutputBindings } from "../shared/chain-outputs.ts";
|
|
37
37
|
import { createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
38
|
-
import { resolveEffectiveAcceptance, validateAcceptanceInput, validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
38
|
+
import { resolveAcceptanceReportMode, resolveEffectiveAcceptance, validateAcceptanceInput, validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
39
39
|
import { createRunFanoutBudget, writeRunFanoutBudgetDescriptor } from "../shared/run-fanout-budget.ts";
|
|
40
40
|
import { validateImplementationToolContract } from "../shared/completion-guard.ts";
|
|
41
41
|
import {
|
|
@@ -70,7 +70,7 @@ import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
|
70
70
|
import { usageBudgetState } from "../shared/usage-budget.ts";
|
|
71
71
|
import type { ImportedAsyncRoot } from "./chain-root-attachment.ts";
|
|
72
72
|
import type { SessionLeaseRequest } from "../shared/session-lease.ts";
|
|
73
|
-
import { finalizeProcessTerminal, readProcessTerminal } from "./process-terminal.ts";
|
|
73
|
+
import { finalizeProcessTerminal, initializeProcessTerminal, readProcessTerminal } from "./process-terminal.ts";
|
|
74
74
|
import type { ActiveAsyncCapacityHandle } from "./active-async-capacity.ts";
|
|
75
75
|
import { statusStepDescription } from "./chain-append.ts";
|
|
76
76
|
import { SUBAGENT_PROCESS_TERMINAL_EVENT } from "../../shared/types.ts";
|
|
@@ -81,7 +81,7 @@ import { normalizeExtensionBindings, omitExtensionBindingsEnv, type ExtensionBin
|
|
|
81
81
|
import { assertWorkflowLaneKey, normalizeWorkflowLaneMetadata } from "../shared/lane-metadata.ts";
|
|
82
82
|
|
|
83
83
|
const require = createRequire(import.meta.url);
|
|
84
|
-
const piPackageRoot = resolvePiPackageRoot();
|
|
84
|
+
const piPackageRoot = resolvePiPackageRoot() ?? resolveInstalledPiPackageRoot();
|
|
85
85
|
|
|
86
86
|
function resolveJitiCliFromPackageJson(packageJsonPath: string): string | undefined {
|
|
87
87
|
if (!fs.existsSync(packageJsonPath)) return undefined;
|
|
@@ -432,13 +432,15 @@ function waitForRunnerStartup(startupPath: string, expectedState: RunnerStartupS
|
|
|
432
432
|
return { ok: false, error: `Timed out after ${timeoutMs}ms waiting for the async runner startup state '${expectedState}'.`, startupDidNotProceed: true };
|
|
433
433
|
}
|
|
434
434
|
|
|
435
|
+
const writePrivateStartupControlJson = createAtomicJsonWriter({ mode: 0o600, ignoreCleanupErrorAfterSuccess: true });
|
|
436
|
+
|
|
435
437
|
function writeRunnerStartupControl(filePath: string, payload: { action: "ack" | "proceed"; token: string }): void {
|
|
436
438
|
// Delegate to the shared atomic JSON writer (temp file + rename, retrying
|
|
437
439
|
// transient Windows EPERM/EBUSY/EACCES locks and cleaning up the temp file
|
|
438
440
|
// on failure), so the startup handshake gets the same locking resilience as
|
|
439
441
|
// every other async control/result file. This is exercised by
|
|
440
442
|
// test/unit/atomic-json.test.ts.
|
|
441
|
-
|
|
443
|
+
writePrivateStartupControlJson(filePath, payload);
|
|
442
444
|
}
|
|
443
445
|
|
|
444
446
|
function runnerIsAlive(pid: number): boolean {
|
|
@@ -520,7 +522,7 @@ export function emitProcessTerminalEvent(ctx: AsyncExecutionContext, proof: unkn
|
|
|
520
522
|
}
|
|
521
523
|
}
|
|
522
524
|
|
|
523
|
-
function spawnRunner(cfg: object, suffix: string, cwd: string, initialStatus: Omit<AsyncStatus, "pid" | "processTerminal">, initialStatusPath: string, onProcessTerminal?: (proof: unknown) => void, requestedCwd = cwd): SpawnRunnerResult {
|
|
525
|
+
function spawnRunner(cfg: object, suffix: string, cwd: string, initialStatus: Omit<AsyncStatus, "pid" | "processTerminal">, initialStatusPath: string, onProcessTerminal?: (proof: unknown) => void, onBeforeProceed?: (runnerProcessInstanceId: string) => void, requestedCwd = cwd): SpawnRunnerResult {
|
|
524
526
|
const cwdError = preflightLaunchCwd(requestedCwd, cwd);
|
|
525
527
|
if (cwdError) return { error: cwdError };
|
|
526
528
|
|
|
@@ -637,6 +639,24 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, initialStatus: Om
|
|
|
637
639
|
});
|
|
638
640
|
} catch (error) {
|
|
639
641
|
const message = `Failed to persist initial async status: ${error instanceof Error ? error.message : String(error)}`;
|
|
642
|
+
if (launchAsyncDir) persistPreProceedStartupFailure(launchAsyncDir, launchRunId, runnerProcessInstanceId, launchSessionId, launchCompletionOwnerId, message);
|
|
643
|
+
const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
|
|
644
|
+
return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
|
|
645
|
+
}
|
|
646
|
+
try {
|
|
647
|
+
if (!launchAsyncDir) throw new Error("Async runner is missing its lifecycle directory.");
|
|
648
|
+
initializeProcessTerminal(launchAsyncDir, launchRunId, runnerProcessInstanceId);
|
|
649
|
+
} catch (error) {
|
|
650
|
+
const message = `Failed to establish async runner lifecycle sidecar: ${error instanceof Error ? error.message : String(error)}`;
|
|
651
|
+
if (launchAsyncDir) persistPreProceedStartupFailure(launchAsyncDir, launchRunId, runnerProcessInstanceId, launchSessionId, launchCompletionOwnerId, message);
|
|
652
|
+
const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
|
|
653
|
+
return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
|
|
654
|
+
}
|
|
655
|
+
try {
|
|
656
|
+
onBeforeProceed?.(runnerProcessInstanceId);
|
|
657
|
+
} catch (error) {
|
|
658
|
+
const message = `Failed to establish async runner capacity ownership: ${error instanceof Error ? error.message : String(error)}`;
|
|
659
|
+
if (launchAsyncDir) persistPreProceedStartupFailure(launchAsyncDir, launchRunId, runnerProcessInstanceId, launchSessionId, launchCompletionOwnerId, message);
|
|
640
660
|
const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
|
|
641
661
|
return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
|
|
642
662
|
}
|
|
@@ -905,6 +925,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
905
925
|
const fast = s.fast ?? params.fast ?? a.fast;
|
|
906
926
|
const toolPlan = resolvePiLaunchToolPlan({
|
|
907
927
|
tools: a.tools,
|
|
928
|
+
excludeTools: a.excludeTools,
|
|
908
929
|
allowNestedSubagents: a.allowNestedSubagents,
|
|
909
930
|
extensions: a.extensions,
|
|
910
931
|
subagentOnlyExtensions: a.subagentOnlyExtensions,
|
|
@@ -967,6 +988,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
967
988
|
...(primaryModelFromParent ? { skipPrimaryModelVerification: true } : {}),
|
|
968
989
|
...(availableModels && availableModels.length > 0 ? { modelVerificationRegistry: availableModels } : {}),
|
|
969
990
|
tools: a.tools,
|
|
991
|
+
excludeTools: a.excludeTools,
|
|
970
992
|
allowNestedSubagents: a.allowNestedSubagents,
|
|
971
993
|
extensions: a.extensions,
|
|
972
994
|
subagentOnlyExtensions: a.subagentOnlyExtensions,
|
|
@@ -1002,7 +1024,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
1002
1024
|
acceptanceRole: a.acceptanceRole,
|
|
1003
1025
|
...(s.gateOn ? { gateOn: s.gateOn } : {}),
|
|
1004
1026
|
...(s.outputSchema ? { structuredOutputSchema: s.outputSchema } : {}),
|
|
1005
|
-
...(s.outputSchema ? { structuredOutput: createStructuredOutputRuntime(s.outputSchema, path.join(asyncDir, "structured-output"), {
|
|
1027
|
+
...(s.outputSchema ? { structuredOutput: createStructuredOutputRuntime(s.outputSchema, path.join(asyncDir, "structured-output"), { acceptanceReport: resolveAcceptanceReportMode(s.acceptance) }) } : {}),
|
|
1006
1028
|
...(resolvedToolBudget.budget ? { toolBudget: resolvedToolBudget.budget } : {}),
|
|
1007
1029
|
...(s.worktree ? { worktree: true } : {}),
|
|
1008
1030
|
};
|
|
@@ -1171,7 +1193,7 @@ export function executeAsyncChain(
|
|
|
1171
1193
|
chain: chain.map((step) => {
|
|
1172
1194
|
if (isParallelStep(step)) return { parallel: step.parallel };
|
|
1173
1195
|
if (isDynamicParallelStep(step)) return { acceptance: step.acceptance, parallel: step.parallel };
|
|
1174
|
-
return { acceptance: step.acceptance };
|
|
1196
|
+
return { acceptance: step.acceptance, outputSchema: step.outputSchema };
|
|
1175
1197
|
}),
|
|
1176
1198
|
});
|
|
1177
1199
|
if (acceptanceErrors.length > 0) return formatAsyncStartError(resultMode, acceptanceErrors.join(" "));
|
|
@@ -1332,6 +1354,7 @@ export function executeAsyncChain(
|
|
|
1332
1354
|
},
|
|
1333
1355
|
path.join(asyncDir, "status.json"),
|
|
1334
1356
|
(proof) => emitProcessTerminalEvent(ctx, proof),
|
|
1357
|
+
(runnerProcessInstanceId) => params.activeAsyncCapacity?.markStarted(runnerProcessInstanceId),
|
|
1335
1358
|
);
|
|
1336
1359
|
} catch (error) {
|
|
1337
1360
|
params.activeAsyncCapacity?.rollback();
|
|
@@ -1340,7 +1363,10 @@ export function executeAsyncChain(
|
|
|
1340
1363
|
}
|
|
1341
1364
|
|
|
1342
1365
|
if (spawnResult.error) {
|
|
1343
|
-
if (
|
|
1366
|
+
if (spawnResult.startupDidNotProceed) {
|
|
1367
|
+
if (!spawnResult.runnerProcessInstanceId || params.activeAsyncCapacity?.rollbackBeforeRunnerProceed(spawnResult.runnerProcessInstanceId) !== true) params.activeAsyncCapacity?.rollback();
|
|
1368
|
+
}
|
|
1369
|
+
else if (!spawnResult.pid || !spawnResult.runnerProcessInstanceId) params.activeAsyncCapacity?.rollback();
|
|
1344
1370
|
else params.activeAsyncCapacity?.markStarted(spawnResult.runnerProcessInstanceId);
|
|
1345
1371
|
return formatAsyncStartError(resultMode, `Failed to start async ${resultMode} '${id}': ${spawnResult.error}`);
|
|
1346
1372
|
}
|
|
@@ -1348,8 +1374,6 @@ export function executeAsyncChain(
|
|
|
1348
1374
|
params.activeAsyncCapacity?.rollback();
|
|
1349
1375
|
return formatAsyncStartError(resultMode, `Failed to start async ${resultMode} '${id}': runner identity unavailable`);
|
|
1350
1376
|
}
|
|
1351
|
-
params.activeAsyncCapacity?.markStarted(spawnResult.runnerProcessInstanceId);
|
|
1352
|
-
|
|
1353
1377
|
if (spawnResult.pid) {
|
|
1354
1378
|
const eventFirstStep = eventChain[0];
|
|
1355
1379
|
if (!eventFirstStep) {
|
|
@@ -1647,7 +1671,7 @@ export function executeAsyncSingle(
|
|
|
1647
1671
|
const initialUsageBudget = usageBudgetState(params.usageBudget, undefined);
|
|
1648
1672
|
const resolvedSessionDir = params.sessionDir ?? (sessionRoot ? path.join(sessionRoot, `async-${id}`) : undefined);
|
|
1649
1673
|
const structuredOutput = params.structuredOutputSchema
|
|
1650
|
-
? createStructuredOutputRuntime(params.structuredOutputSchema, path.join(asyncDir, "structured-output"), {
|
|
1674
|
+
? createStructuredOutputRuntime(params.structuredOutputSchema, path.join(asyncDir, "structured-output"), { acceptanceReport: resolveAcceptanceReportMode(params.acceptance) })
|
|
1651
1675
|
: undefined;
|
|
1652
1676
|
let modelCandidates: string[] = [];
|
|
1653
1677
|
if (!externalRunner) {
|
|
@@ -1667,6 +1691,7 @@ export function executeAsyncSingle(
|
|
|
1667
1691
|
}
|
|
1668
1692
|
const toolPlan = resolvePiLaunchToolPlan({
|
|
1669
1693
|
tools: agentConfig.tools,
|
|
1694
|
+
excludeTools: agentConfig.excludeTools,
|
|
1670
1695
|
allowNestedSubagents: agentConfig.allowNestedSubagents,
|
|
1671
1696
|
extensions: agentConfig.extensions,
|
|
1672
1697
|
subagentOnlyExtensions: agentConfig.subagentOnlyExtensions,
|
|
@@ -1713,6 +1738,7 @@ export function executeAsyncSingle(
|
|
|
1713
1738
|
inheritSkills: agentConfig.inheritSkills,
|
|
1714
1739
|
skills: resolvedSkills.map((skill) => skill.name),
|
|
1715
1740
|
tools: toolPlan.effectiveToolAllowlist,
|
|
1741
|
+
...(toolPlan.excludeTools.length > 0 ? { excludeTools: toolPlan.excludeTools } : {}),
|
|
1716
1742
|
extensions: toolPlan.extensionArgs,
|
|
1717
1743
|
mcpDirectTools: toolPlan.effectiveMcpTools,
|
|
1718
1744
|
...(outputPath ? { outputPath } : {}),
|
|
@@ -1751,6 +1777,7 @@ export function executeAsyncSingle(
|
|
|
1751
1777
|
...(effectiveThinking ? { thinking: resolveEffectiveThinking(model, effectiveThinking) } : {}),
|
|
1752
1778
|
...(thinkingCeiling ? { thinkingCeiling } : {}),
|
|
1753
1779
|
...(recoveryAgentConfig.tools ? { tools: [...recoveryAgentConfig.tools] } : {}),
|
|
1780
|
+
...(recoveryAgentConfig.excludeTools ? { excludeTools: [...recoveryAgentConfig.excludeTools] } : {}),
|
|
1754
1781
|
...(recoveryAgentConfig.allowNestedSubagents !== undefined ? { allowNestedSubagents: recoveryAgentConfig.allowNestedSubagents } : {}),
|
|
1755
1782
|
...(recoveryAgentConfig.extensions ? { extensions: [...recoveryAgentConfig.extensions] } : {}),
|
|
1756
1783
|
...(recoveryAgentConfig.subagentOnlyExtensions ? { subagentOnlyExtensions: [...recoveryAgentConfig.subagentOnlyExtensions] } : {}),
|
|
@@ -1818,6 +1845,7 @@ export function executeAsyncSingle(
|
|
|
1818
1845
|
...(modelOrigin === "inherited" ? { skipPrimaryModelVerification: true } : {}),
|
|
1819
1846
|
...(availableModels && availableModels.length > 0 ? { modelVerificationRegistry: availableModels } : {}),
|
|
1820
1847
|
tools: agentConfig.tools,
|
|
1848
|
+
excludeTools: agentConfig.excludeTools,
|
|
1821
1849
|
allowNestedSubagents: agentConfig.allowNestedSubagents,
|
|
1822
1850
|
extensions: agentConfig.extensions,
|
|
1823
1851
|
subagentOnlyExtensions: agentConfig.subagentOnlyExtensions,
|
|
@@ -1846,6 +1874,7 @@ export function executeAsyncSingle(
|
|
|
1846
1874
|
...(extensionBindings ? { extensionBindings } : {}),
|
|
1847
1875
|
launchResolvedExtensions,
|
|
1848
1876
|
effectiveAcceptance: resolvedAcceptance,
|
|
1877
|
+
acceptanceInput: params.acceptance,
|
|
1849
1878
|
...(structuredOutput ? { structuredOutput } : {}),
|
|
1850
1879
|
...(params.structuredOutputSchema ? { structuredOutputSchema: params.structuredOutputSchema } : {}),
|
|
1851
1880
|
...(resolvedToolBudget.budget ? { toolBudget: resolvedToolBudget.budget } : {}),
|
|
@@ -1914,6 +1943,7 @@ export function executeAsyncSingle(
|
|
|
1914
1943
|
},
|
|
1915
1944
|
path.join(asyncDir, "status.json"),
|
|
1916
1945
|
(proof) => emitProcessTerminalEvent(ctx, proof),
|
|
1946
|
+
(runnerProcessInstanceId) => params.activeAsyncCapacity?.markStarted(runnerProcessInstanceId),
|
|
1917
1947
|
params.requestedCwd ?? runnerCwd,
|
|
1918
1948
|
);
|
|
1919
1949
|
} catch (error) {
|
|
@@ -1923,7 +1953,10 @@ export function executeAsyncSingle(
|
|
|
1923
1953
|
}
|
|
1924
1954
|
|
|
1925
1955
|
if (spawnResult.error) {
|
|
1926
|
-
if (
|
|
1956
|
+
if (spawnResult.startupDidNotProceed) {
|
|
1957
|
+
if (!spawnResult.runnerProcessInstanceId || params.activeAsyncCapacity?.rollbackBeforeRunnerProceed(spawnResult.runnerProcessInstanceId) !== true) params.activeAsyncCapacity?.rollback();
|
|
1958
|
+
}
|
|
1959
|
+
else if (!spawnResult.pid || !spawnResult.runnerProcessInstanceId) params.activeAsyncCapacity?.rollback();
|
|
1927
1960
|
else params.activeAsyncCapacity?.markStarted(spawnResult.runnerProcessInstanceId);
|
|
1928
1961
|
return formatAsyncStartError("single", `Failed to start async run '${id}': ${spawnResult.error}`);
|
|
1929
1962
|
}
|
|
@@ -1931,8 +1964,6 @@ export function executeAsyncSingle(
|
|
|
1931
1964
|
params.activeAsyncCapacity?.rollback();
|
|
1932
1965
|
return formatAsyncStartError("single", `Failed to start async run '${id}': runner identity unavailable`);
|
|
1933
1966
|
}
|
|
1934
|
-
params.activeAsyncCapacity?.markStarted(spawnResult.runnerProcessInstanceId);
|
|
1935
|
-
|
|
1936
1967
|
if (spawnResult.pid) {
|
|
1937
1968
|
if (inheritedNestedRoute && nestedAddress) {
|
|
1938
1969
|
const now = Date.now();
|
|
@@ -315,7 +315,7 @@ export function readAsyncRecoveryDescriptor(asyncDir: string | undefined): Steer
|
|
|
315
315
|
const parsed = value as Record<string, unknown>;
|
|
316
316
|
const allowedFields = new Set([
|
|
317
317
|
"version", "launchContractDigest", "sourceRunId", "agentContract", "agent", "sessionFile", "cwd", "model", "modelProvider", "modelOverrideFromParent", "modelOrigin", "fallbackModels", "thinking", "thinkingCeiling", "tools", "allowNestedSubagents", "extensions",
|
|
318
|
-
"subagentOnlyExtensions", "mcpDirectTools", "mutationTools", "systemPrompt", "systemPromptMode", "inheritProjectContext", "inheritGlobalContext", "inheritSkills", "skills",
|
|
318
|
+
"subagentOnlyExtensions", "mcpDirectTools", "excludeTools", "mutationTools", "systemPrompt", "systemPromptMode", "inheritProjectContext", "inheritGlobalContext", "inheritSkills", "skills",
|
|
319
319
|
"skillPath", "agentFilePath", "completionGuard", "memory", "outputPath", "outputMode", "structuredOutputSchema", "acceptance", "sessionDir", "artifactConfig",
|
|
320
320
|
"artifactsDir", "maxOutput", "controlConfig", "context", "intercomBridge", "absoluteDeadlineAt", "initialTurnBudget", "initialToolBudget", "maxSubagentDepth", "share", "capabilityCeiling",
|
|
321
321
|
"launchResolvedExtensions", "runFanoutBudget", "lane",
|
|
@@ -356,7 +356,7 @@ export function readAsyncRecoveryDescriptor(asyncDir: string | undefined): Steer
|
|
|
356
356
|
else if (typeof parsed.inheritGlobalContext !== "boolean") throw new Error(`Invalid async recovery descriptor '${descriptorPath}': inheritGlobalContext must be a boolean.`);
|
|
357
357
|
if (parsed.allowNestedSubagents !== undefined && typeof parsed.allowNestedSubagents !== "boolean") throw new Error(`Invalid async recovery descriptor '${descriptorPath}': allowNestedSubagents must be a boolean.`);
|
|
358
358
|
if (!Number.isInteger(parsed.maxSubagentDepth) || (parsed.maxSubagentDepth as number) < 0) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': maxSubagentDepth must be a non-negative integer.`);
|
|
359
|
-
for (const field of ["fallbackModels", "tools", "extensions", "subagentOnlyExtensions", "mcpDirectTools", "mutationTools", "skills", "skillPath"] as const) {
|
|
359
|
+
for (const field of ["fallbackModels", "tools", "excludeTools", "extensions", "subagentOnlyExtensions", "mcpDirectTools", "mutationTools", "skills", "skillPath"] as const) {
|
|
360
360
|
const item = parsed[field];
|
|
361
361
|
if (item !== undefined && (!Array.isArray(item) || item.some((entry) => typeof entry !== "string" || !entry.trim()))) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': ${field} must contain non-empty strings.`);
|
|
362
362
|
}
|
|
@@ -590,6 +590,7 @@ export function applySteeringRecoveryAgentConfig(agentConfig: AgentConfig, descr
|
|
|
590
590
|
thinking: descriptor.thinking,
|
|
591
591
|
maxThinking: intersectThinkingCeilings(descriptor.thinkingCeiling, agentConfig.maxThinking),
|
|
592
592
|
tools: descriptor.tools ? [...descriptor.tools] : undefined,
|
|
593
|
+
excludeTools: descriptor.excludeTools ? [...descriptor.excludeTools] : undefined,
|
|
593
594
|
allowNestedSubagents: descriptor.allowNestedSubagents,
|
|
594
595
|
extensions: descriptor.extensions ? [...descriptor.extensions] : undefined,
|
|
595
596
|
subagentOnlyExtensions: descriptor.subagentOnlyExtensions ? [...descriptor.subagentOnlyExtensions] : undefined,
|
|
@@ -115,6 +115,22 @@ export function writeProcessTerminalCandidate(asyncDir: string, candidate: Proce
|
|
|
115
115
|
writePrivateAtomicJson(processTerminalCandidatePath(asyncDir), candidate);
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
/** Establish ownership before authorizing a runner to launch any child process. */
|
|
119
|
+
export function initializeProcessTerminal(asyncDir: string, runId: string, runnerProcessInstanceId: string): void {
|
|
120
|
+
writeProcessTerminalCandidate(asyncDir, {
|
|
121
|
+
version: 1,
|
|
122
|
+
runId,
|
|
123
|
+
runnerProcessInstanceId,
|
|
124
|
+
writers: {},
|
|
125
|
+
});
|
|
126
|
+
writeAtomicJson(processTerminalPath(asyncDir), {
|
|
127
|
+
version: 1,
|
|
128
|
+
state: "pending",
|
|
129
|
+
runId,
|
|
130
|
+
runnerProcessInstanceId,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
118
134
|
export function markProcessTerminalCandidateLeaseRelease(asyncDir: string, token: string, acknowledged: boolean): void {
|
|
119
135
|
const candidate = readProcessTerminalCandidate(asyncDir);
|
|
120
136
|
if (!candidate || candidate.revivalLeaseToken !== token) return;
|
|
@@ -52,6 +52,8 @@ export interface ScheduleRecord {
|
|
|
52
52
|
catchUp: "none" | "latest";
|
|
53
53
|
timeoutMs?: number;
|
|
54
54
|
paused: boolean;
|
|
55
|
+
sessionOnly?: boolean;
|
|
56
|
+
ownerSessionFile?: string;
|
|
55
57
|
createdAt: string;
|
|
56
58
|
updatedAt: string;
|
|
57
59
|
activeRunId?: string;
|
|
@@ -72,6 +74,8 @@ export interface ScheduleRunRecord {
|
|
|
72
74
|
error?: string;
|
|
73
75
|
}
|
|
74
76
|
|
|
77
|
+
type PublicScheduleRecord = Omit<ScheduleRecord, "ownerSessionFile">;
|
|
78
|
+
|
|
75
79
|
type ScheduledRunManagerDeps = {
|
|
76
80
|
config: ExtensionConfig;
|
|
77
81
|
launch(params: SubagentParamsLike, ctx: ExtensionContext, signal: AbortSignal): Promise<AgentToolResult<Details>>;
|
|
@@ -295,6 +299,8 @@ function parseSchedule(value: unknown, file: string): ScheduleRecord {
|
|
|
295
299
|
} else if (record.trigger.kind === "interval") {
|
|
296
300
|
if (typeof record.trigger.every !== "string" || typeof record.trigger.everyMs !== "number" || typeof record.trigger.anchorAt !== "string" || typeof record.trigger.nextRunAt !== "string") throw new Error(`Schedule record '${file}' has an invalid interval trigger.`);
|
|
297
301
|
} else throw new Error(`Schedule record '${file}' has an unsupported trigger.`);
|
|
302
|
+
if (record.sessionOnly !== undefined && typeof record.sessionOnly !== "boolean") throw new Error(`Schedule record '${file}' has invalid sessionOnly.`);
|
|
303
|
+
if (record.sessionOnly === true && (typeof record.ownerSessionFile !== "string" || !record.ownerSessionFile.trim())) throw new Error(`Schedule record '${file}' is session-only but has no owner session file.`);
|
|
298
304
|
return { ...record, target: parseScheduleTarget(record.target, file) } as ScheduleRecord;
|
|
299
305
|
}
|
|
300
306
|
|
|
@@ -399,13 +405,19 @@ function duePlannedAt(schedule: ScheduleRecord, now: number): number | undefined
|
|
|
399
405
|
}
|
|
400
406
|
|
|
401
407
|
function textResult(text: string, schedules?: ScheduleRecord[], runs?: ScheduleRunRecord[], isError = false): AgentToolResult<Details> {
|
|
408
|
+
const publicSchedules = schedules?.map(publicScheduleRecord);
|
|
402
409
|
return {
|
|
403
410
|
content: [{ type: "text", text }],
|
|
404
411
|
...(isError ? { isError: true } : {}),
|
|
405
|
-
details: { mode: "management", results: [], schedules: { ...(
|
|
412
|
+
details: { mode: "management", results: [], schedules: { ...(publicSchedules ? { records: publicSchedules } : {}), ...(runs ? { runs } : {}) } },
|
|
406
413
|
};
|
|
407
414
|
}
|
|
408
415
|
|
|
416
|
+
function publicScheduleRecord(schedule: ScheduleRecord): PublicScheduleRecord {
|
|
417
|
+
const { ownerSessionFile: _ownerSessionFile, ...rest } = schedule;
|
|
418
|
+
return rest;
|
|
419
|
+
}
|
|
420
|
+
|
|
409
421
|
function targetLabel(target: ScheduleTarget): string {
|
|
410
422
|
const preview = previewSimpleWorkflowRun(target.workflowScript);
|
|
411
423
|
return preview?.agent ? `workflowScript -> agent ${preview.agent}` : "workflowScript (dynamic)";
|
|
@@ -450,6 +462,32 @@ function snapshotContext(ctx: ExtensionContext, cwd: string): ExtensionContext {
|
|
|
450
462
|
return { ...ctx, cwd, sessionManager };
|
|
451
463
|
}
|
|
452
464
|
|
|
465
|
+
/**
|
|
466
|
+
* 规范化会话文件路径, 兼容 Windows 路径大小写差异.
|
|
467
|
+
*
|
|
468
|
+
* @param value 会话文件路径
|
|
469
|
+
* @returns 规范化后的路径, 空值时返回 undefined
|
|
470
|
+
*/
|
|
471
|
+
function normalizedSessionFile(value: string | undefined): string | undefined {
|
|
472
|
+
if (!value || !value.trim()) return undefined;
|
|
473
|
+
const normalized = path.normalize(path.resolve(value));
|
|
474
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* 判断 Schedule 是否属于当前 Pi 会话.
|
|
479
|
+
*
|
|
480
|
+
* @param schedule Schedule 记录
|
|
481
|
+
* @param ctx 当前 Pi 会话上下文
|
|
482
|
+
* @returns 是否允许当前会话执行该 Schedule
|
|
483
|
+
*/
|
|
484
|
+
function scheduleBelongsToSession(schedule: ScheduleRecord, ctx: ExtensionContext): boolean {
|
|
485
|
+
if (schedule.sessionOnly !== true) return true;
|
|
486
|
+
const ownerSessionFile = normalizedSessionFile(schedule.ownerSessionFile);
|
|
487
|
+
const currentSessionFile = normalizedSessionFile(ctx.sessionManager.getSessionFile());
|
|
488
|
+
return ownerSessionFile !== undefined && ownerSessionFile === currentSessionFile;
|
|
489
|
+
}
|
|
490
|
+
|
|
453
491
|
export function listScheduledRunSummaries(cwd: string, root?: string): ScheduleRecord[] {
|
|
454
492
|
return new ScheduleStore(scheduledRunStorePath(cwd, undefined, root), root === undefined ? path.resolve(cwd) : undefined).list();
|
|
455
493
|
}
|
|
@@ -560,6 +598,10 @@ export class ScheduledRunManager {
|
|
|
560
598
|
if (params.catchUp !== undefined && params.catchUp !== "none" && params.catchUp !== "latest") return textResult("catchUp must be 'none' or 'latest'.", undefined, undefined, true);
|
|
561
599
|
if (params.missionId !== undefined || params.mission !== undefined || params.missionUpdate !== undefined || params.missionStatus !== undefined || params.missionScope !== undefined) return textResult("Mission attachment is deferred from this first schedule slice.", undefined, undefined, true);
|
|
562
600
|
if (params.on !== undefined || params.timezone !== undefined || every === "day" || every === "week" || every === "month" || every === "year") return textResult("Calendar schedules are deferred from this first safe slice. Use a fixed interval such as every:'24h' or every:'7d'.", undefined, undefined, true);
|
|
601
|
+
const sessionOnly = params.sessionOnly === true;
|
|
602
|
+
if (sessionOnly && params.cwd !== undefined && !samePath(params.cwd, ctx.cwd)) return textResult("sessionOnly schedules cannot use an explicit cross-project cwd.", undefined, undefined, true);
|
|
603
|
+
const ownerSessionFile = sessionOnly ? ctx.sessionManager.getSessionFile() : undefined;
|
|
604
|
+
if (sessionOnly && !ownerSessionFile) return textResult("sessionOnly schedules require a persisted current session.", undefined, undefined, true);
|
|
563
605
|
const sessionId = ctx.sessionManager.getSessionId() ?? "unknown";
|
|
564
606
|
if (this.deps.resolveCapabilityCeiling?.(sessionId)) return textResult("Cannot persist a schedule while a capability ceiling is active.", undefined, undefined, true);
|
|
565
607
|
const pendingCount = store.list().filter(hasPendingScheduleWork).length;
|
|
@@ -587,24 +629,25 @@ export class ScheduledRunManager {
|
|
|
587
629
|
catchUp: params.catchUp ?? "latest",
|
|
588
630
|
...(params.timeoutMs === undefined ? {} : { timeoutMs: params.timeoutMs }),
|
|
589
631
|
paused: false,
|
|
632
|
+
...(sessionOnly ? { sessionOnly: true, ownerSessionFile: path.resolve(ownerSessionFile!) } : {}),
|
|
590
633
|
createdAt: timestamp(now),
|
|
591
634
|
updatedAt: timestamp(now),
|
|
592
635
|
};
|
|
593
636
|
store.write(schedule);
|
|
594
637
|
store.appendEvent(schedule, "schedule.created");
|
|
595
638
|
this.arm(schedule, store);
|
|
596
|
-
return textResult(`Created schedule ${id}.\nName: ${schedule.name}\nTrigger: ${at ? `at ${at}` : `every ${every}`}\nNext: ${schedule.trigger.nextRunAt}\nTarget: ${targetLabel(schedule.target)}`, [schedule]);
|
|
639
|
+
return textResult(`Created schedule ${id}.\nName: ${schedule.name}\nTrigger: ${at ? `at ${at}` : `every ${every}`}\nSession only: ${schedule.sessionOnly === true ? "yes" : "no"}\nNext: ${schedule.trigger.nextRunAt}\nTarget: ${targetLabel(schedule.target)}`, [schedule]);
|
|
597
640
|
}
|
|
598
641
|
|
|
599
642
|
private list(): AgentToolResult<Details> {
|
|
600
643
|
const schedules = this.requireStore().list().sort((a, b) => (a.trigger.nextRunAt ?? "").localeCompare(b.trigger.nextRunAt ?? ""));
|
|
601
644
|
if (!schedules.length) return textResult("No project schedules.", []);
|
|
602
|
-
return textResult([`Project schedules: ${schedules.length}`, ...schedules.map((item) => `- ${item.id} | ${item.paused ? "paused" : item.activeRunId ? "running" : "scheduled"} | ${item.trigger.nextRunAt ?? "no next run"} | ${item.name}`)].join("\n"), schedules);
|
|
645
|
+
return textResult([`Project schedules: ${schedules.length}`, ...schedules.map((item) => `- ${item.id} | ${item.paused ? "paused" : item.activeRunId ? "running" : "scheduled"} | ${item.trigger.nextRunAt ?? "no next run"} | ${item.sessionOnly === true ? "session-only" : "project"} | ${item.name}`)].join("\n"), schedules);
|
|
603
646
|
}
|
|
604
647
|
|
|
605
648
|
private show(params: SubagentParamsLike): AgentToolResult<Details> {
|
|
606
649
|
const schedule = this.resolve(params);
|
|
607
|
-
return textResult([`Schedule: ${schedule.id}`, `Name: ${schedule.name}`, `State: ${schedule.paused ? "paused" : schedule.activeRunId ? "running" : "scheduled"}`, `Target: ${targetLabel(schedule.target)}`, `CWD: ${shortenPath(schedule.cwd)}`, `Next: ${schedule.trigger.nextRunAt ?? "none"}`, `Catch up: ${schedule.catchUp}`, schedule.activeRunId ? `Active run: ${schedule.activeRunId}` : undefined].filter(Boolean).join("\n"), [schedule]);
|
|
650
|
+
return textResult([`Schedule: ${schedule.id}`, `Name: ${schedule.name}`, `State: ${schedule.paused ? "paused" : schedule.activeRunId ? "running" : "scheduled"}`, `Session only: ${schedule.sessionOnly === true ? "yes" : "no"}`, `Target: ${targetLabel(schedule.target)}`, `CWD: ${shortenPath(schedule.cwd)}`, `Next: ${schedule.trigger.nextRunAt ?? "none"}`, `Catch up: ${schedule.catchUp}`, schedule.activeRunId ? `Active run: ${schedule.activeRunId}` : undefined].filter(Boolean).join("\n"), [schedule]);
|
|
608
651
|
}
|
|
609
652
|
|
|
610
653
|
private history(params: SubagentParamsLike): AgentToolResult<Details> {
|
|
@@ -628,13 +671,18 @@ export class ScheduledRunManager {
|
|
|
628
671
|
private async runManual(params: SubagentParamsLike): Promise<AgentToolResult<Details>> {
|
|
629
672
|
const store = this.requireStore();
|
|
630
673
|
const schedule = this.resolve(params);
|
|
674
|
+
const context = this.requireContext(store);
|
|
675
|
+
if (!scheduleBelongsToSession(schedule, context)) {
|
|
676
|
+
return textResult(`Skipped schedule ${schedule.id}: current session is not its owner.`, [schedule]);
|
|
677
|
+
}
|
|
631
678
|
const run = await this.launch(store, schedule, this.now(), "manual", false);
|
|
632
679
|
return textResult(`Manual schedule run ${run.id}: ${run.state}${run.asyncId ? ` (async ${run.asyncId})` : ""}.`, [store.get(schedule.id)], [run], run.state === "failed_launch");
|
|
633
680
|
}
|
|
634
681
|
|
|
635
682
|
private async runDue(): Promise<AgentToolResult<Details>> {
|
|
636
683
|
const store = this.requireStore();
|
|
637
|
-
const
|
|
684
|
+
const context = this.requireContext(store);
|
|
685
|
+
const due = store.list().filter((schedule) => scheduleBelongsToSession(schedule, context) && !schedule.paused && nextRunAt(schedule) !== undefined && nextRunAt(schedule)! <= this.now());
|
|
638
686
|
const runs: ScheduleRunRecord[] = [];
|
|
639
687
|
for (const schedule of due) {
|
|
640
688
|
const planned = duePlannedAt(schedule, this.now())!;
|
|
@@ -659,6 +707,7 @@ export class ScheduledRunManager {
|
|
|
659
707
|
}
|
|
660
708
|
|
|
661
709
|
private restoreOne(store: ScheduleStore, schedule: ScheduleRecord, notBefore?: number, rearm = true): void {
|
|
710
|
+
if (!scheduleBelongsToSession(schedule, this.requireContext(store))) return;
|
|
662
711
|
if (schedule.activeRunId) {
|
|
663
712
|
const run = store.history(schedule.id).find((item) => item.id === schedule.activeRunId);
|
|
664
713
|
if (run?.state === "running" && run.asyncId) this.observedAsyncIds.add(run.asyncId);
|
|
@@ -737,6 +786,7 @@ export class ScheduledRunManager {
|
|
|
737
786
|
// is then nothing to run and nothing to re-arm.
|
|
738
787
|
const schedule = store.find(id);
|
|
739
788
|
if (!schedule) return;
|
|
789
|
+
if (!scheduleBelongsToSession(schedule, this.requireContext(store))) return;
|
|
740
790
|
const planned = duePlannedAt(schedule, this.now());
|
|
741
791
|
if (planned === undefined || schedule.paused) return;
|
|
742
792
|
if (planned > this.now()) return this.arm(schedule, store);
|
|
@@ -857,13 +907,20 @@ export class ScheduledRunManager {
|
|
|
857
907
|
private selectProject(cwd: string, ctx: ExtensionContext): void {
|
|
858
908
|
const projectCwd = path.resolve(cwd);
|
|
859
909
|
const root = scheduledRunStorePath(projectCwd, undefined, this.deps.storeRoot);
|
|
860
|
-
|
|
910
|
+
const isBoundContext = path.resolve(ctx.cwd) === projectCwd;
|
|
911
|
+
const previousContext = this.contexts.get(root);
|
|
912
|
+
const contextChanged = isBoundContext
|
|
913
|
+
&& previousContext !== undefined
|
|
914
|
+
&& normalizedSessionFile(previousContext.sessionManager.getSessionFile()) !== normalizedSessionFile(ctx.sessionManager.getSessionFile());
|
|
915
|
+
if (isBoundContext) this.contexts.set(root, snapshotContext(ctx, projectCwd));
|
|
861
916
|
else if (!this.contexts.has(root)) throw new Error(`Cannot use project '${projectCwd}' until that project has been opened in this runtime.`);
|
|
862
917
|
let store = this.stores.get(root);
|
|
863
918
|
if (!store) {
|
|
864
919
|
store = new ScheduleStore(root, this.deps.storeRoot === undefined ? projectCwd : undefined);
|
|
865
920
|
this.stores.set(root, store);
|
|
866
921
|
this.restore(store);
|
|
922
|
+
} else if (contextChanged) {
|
|
923
|
+
this.restore(store);
|
|
867
924
|
}
|
|
868
925
|
this.store = store;
|
|
869
926
|
}
|
|
@@ -23,7 +23,10 @@ export function steeringMessagePreview(message: string): string {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export function steeringReceipt(message: string, receipt: string): string {
|
|
26
|
-
|
|
26
|
+
const preview = steeringMessagePreview(message);
|
|
27
|
+
const longestFence = Math.max(2, ...[...preview.matchAll(/`{3,}/g)].map((match) => match[0]!.length));
|
|
28
|
+
const fence = "`".repeat(longestFence + 1);
|
|
29
|
+
return `${receipt}\n\nMessage sent:\n${fence}text\n${preview}\n${fence}`;
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
export function createSteeringStatus(): SteeringStatus {
|
|
@@ -77,9 +77,10 @@ import {
|
|
|
77
77
|
} from "../shared/parallel-utils.ts";
|
|
78
78
|
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, deriveForkPromptCacheKey, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan, type SubagentTaskDelivery } from "../shared/pi-args.ts";
|
|
79
79
|
import { deriveChildSessionName } from "../../shared/child-session-name.ts";
|
|
80
|
+
import { alignForkedSessionCwd } from "../../shared/fork-session-cwd.ts";
|
|
80
81
|
import { readRuntimeAcknowledgedExtensions } from "../shared/runtime-acknowledged-extensions.ts";
|
|
81
82
|
import { outputEntryFromAsyncResult, resolveOutputReferences } from "../shared/chain-outputs.ts";
|
|
82
|
-
import { createStructuredOutputRuntime, MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput, readStructuredOutputAcceptanceReport } from "../shared/structured-output.ts";
|
|
83
|
+
import { clearStructuredOutputCaptures, createStructuredOutputRuntime, MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput, readStructuredOutputAcceptanceReport } from "../shared/structured-output.ts";
|
|
83
84
|
import { formatMidToolExitError, formatProcessSignalError, isOrdinaryToolForMidToolExit, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
|
|
84
85
|
import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
|
|
85
86
|
import { buildTimeoutRecoverySummary, collectTrackedMutationEvidence, snapshotTrackedMutations } from "../shared/mutation-evidence.ts";
|
|
@@ -130,7 +131,7 @@ import { assertThinkingWithinCeiling, decodeThinkingCeiling, SUBAGENT_THINKING_C
|
|
|
130
131
|
import { launchBindingDigest } from "../../shared/launch-contract.ts";
|
|
131
132
|
import { writeInitialProgressFile } from "../../shared/settings.ts";
|
|
132
133
|
import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.ts";
|
|
133
|
-
import { acceptanceFailureMessage, aggregateAcceptanceReport, buildSkippedAcceptanceLedger, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport } from "../shared/acceptance.ts";
|
|
134
|
+
import { acceptanceFailureMessage, aggregateAcceptanceReport, buildSkippedAcceptanceLedger, evaluateAcceptance, formatAcceptancePrompt, resolveAcceptanceReportMode, resolveEffectiveAcceptance, stripAcceptanceReport } from "../shared/acceptance.ts";
|
|
134
135
|
import { attachContractProjections, isAgentContractV1 } from "../shared/agent-contract.ts";
|
|
135
136
|
import { waitForImportedAsyncRoot } from "./chain-root-attachment.ts";
|
|
136
137
|
import { normalizeExtensionBindings } from "../shared/extension-bindings.ts";
|
|
@@ -1397,7 +1398,7 @@ async function runSingleStepInner(
|
|
|
1397
1398
|
}
|
|
1398
1399
|
|
|
1399
1400
|
const effectiveStructuredOutput = step.structuredOutput ?? (step.structuredOutputSchema
|
|
1400
|
-
? createStructuredOutputRuntime(step.structuredOutputSchema, path.join(path.dirname(ctx.outputFile), "structured-output"))
|
|
1401
|
+
? createStructuredOutputRuntime(step.structuredOutputSchema, path.join(path.dirname(ctx.outputFile), "structured-output"), { acceptanceReport: resolveAcceptanceReportMode(step.acceptanceInput) })
|
|
1401
1402
|
: undefined);
|
|
1402
1403
|
const placeholderRegex = new RegExp(ctx.placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g");
|
|
1403
1404
|
let task = step.task.replace(placeholderRegex, () => ctx.previousOutput);
|
|
@@ -1407,6 +1408,7 @@ async function runSingleStepInner(
|
|
|
1407
1408
|
if (!step.runner) {
|
|
1408
1409
|
resolvedTaskToolPlan = resolvePiLaunchToolPlan(omitUndefinedProperties({
|
|
1409
1410
|
tools: step.tools,
|
|
1411
|
+
excludeTools: step.excludeTools,
|
|
1410
1412
|
allowNestedSubagents: step.allowNestedSubagents,
|
|
1411
1413
|
extensions: step.extensions,
|
|
1412
1414
|
subagentOnlyExtensions: step.subagentOnlyExtensions,
|
|
@@ -1627,6 +1629,9 @@ async function runSingleStepInner(
|
|
|
1627
1629
|
const effectiveCwd = step.cwd ?? ctx.cwd;
|
|
1628
1630
|
const cwdError = preflightLaunchCwd(step.requestedCwd ?? effectiveCwd, effectiveCwd);
|
|
1629
1631
|
if (cwdError) return { agent: step.agent, output: cwdError, error: cwdError, exitCode: 1, context: step.context };
|
|
1632
|
+
if (step.context === "fork" && step.sessionFile && fs.existsSync(step.sessionFile)) {
|
|
1633
|
+
alignForkedSessionCwd(step.sessionFile, effectiveCwd);
|
|
1634
|
+
}
|
|
1630
1635
|
|
|
1631
1636
|
const candidates = step.modelCandidates !== undefined
|
|
1632
1637
|
? step.modelCandidates.length > 0 ? step.modelCandidates : [undefined]
|
|
@@ -1681,10 +1686,9 @@ async function runSingleStepInner(
|
|
|
1681
1686
|
}));
|
|
1682
1687
|
const outputSnapshot = captureSingleOutputSnapshot(step.outputPath);
|
|
1683
1688
|
if (effectiveStructuredOutput) {
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
// Missing/stale structured-output files are handled after the child exits.
|
|
1689
|
+
const cleanupError = clearStructuredOutputCaptures(effectiveStructuredOutput);
|
|
1690
|
+
if (cleanupError) {
|
|
1691
|
+
return omitUndefinedProperties({ agent: step.agent, output: cleanupError, error: cleanupError, exitCode: 1, context: step.context });
|
|
1688
1692
|
}
|
|
1689
1693
|
}
|
|
1690
1694
|
const watchdogConfig = resolveWatchdogConfig(step.cwd ?? ctx.cwd);
|
|
@@ -1712,6 +1716,7 @@ async function runSingleStepInner(
|
|
|
1712
1716
|
inheritSkills: step.inheritSkills,
|
|
1713
1717
|
requireReadTool: Boolean(step.skills?.length),
|
|
1714
1718
|
tools: step.tools,
|
|
1719
|
+
excludeTools: step.excludeTools,
|
|
1715
1720
|
allowNestedSubagents: step.allowNestedSubagents,
|
|
1716
1721
|
extensions: step.extensions,
|
|
1717
1722
|
subagentOnlyExtensions: step.subagentOnlyExtensions,
|
|
@@ -1761,6 +1766,7 @@ async function runSingleStepInner(
|
|
|
1761
1766
|
if (step.definitionDigest) {
|
|
1762
1767
|
const toolPlan = resolvedTaskToolPlan ?? resolvePiLaunchToolPlan(omitUndefinedProperties({
|
|
1763
1768
|
tools: step.tools,
|
|
1769
|
+
excludeTools: step.excludeTools,
|
|
1764
1770
|
allowNestedSubagents: step.allowNestedSubagents,
|
|
1765
1771
|
extensions: step.extensions,
|
|
1766
1772
|
subagentOnlyExtensions: step.subagentOnlyExtensions,
|
|
@@ -1793,6 +1799,7 @@ async function runSingleStepInner(
|
|
|
1793
1799
|
inheritSkills: step.inheritSkills,
|
|
1794
1800
|
skills: step.skills,
|
|
1795
1801
|
tools: toolPlan.effectiveToolAllowlist,
|
|
1802
|
+
...(toolPlan.excludeTools.length > 0 ? { excludeTools: toolPlan.excludeTools } : {}),
|
|
1796
1803
|
extensions: toolPlan.extensionArgs,
|
|
1797
1804
|
mcpDirectTools: toolPlan.effectiveMcpTools,
|
|
1798
1805
|
...(step.outputPath ? { outputPath: step.outputPath } : {}),
|
|
@@ -23,7 +23,7 @@ Ordinary async subagent runs already notify this session natively when they comp
|
|
|
23
23
|
• { stopOnAttention: false } — for blocking waits only, keep waiting through idle or long-thinking attention; supervisor/contact requests still stop the wait.
|
|
24
24
|
• { timeoutMs: 600000 } — stop waiting after N ms; active work keeps running. Omitted values use waitTool.defaultTimeoutMs, then 30 minutes. Window expiry returns a non-error window_elapsed result with active work identities.
|
|
25
25
|
|
|
26
|
-
Non-blocking subscriptions are visible in subagent status and differ from disabling waitTool: waitTool.enabled=false returns immediately without registering any future wake. Provider jobs are session-scoped and identified exactly, so replacing one job with another cannot hide a completion. Provider extensions must be explicitly loaded in this process. In a child agent, keep \`bg_wait\`
|
|
26
|
+
Non-blocking subscriptions are visible in subagent status and differ from disabling waitTool: waitTool.enabled=false returns immediately without registering any future wake. Provider jobs are session-scoped and identified exactly, so replacing one job with another cannot hide a completion. Provider extensions must be explicitly loaded in this process. In a child agent, keep \`bg_wait\` in the child tool allowlist and load each provider through the agent's extensions or subagentOnlyExtensions; this tool never loads providers or grants tools itself.${enabled ? "" : "\n\nConfigured behavior: bg_wait is disabled by config.waitTool or PI_SUBAGENT_WAIT_TOOL_ENABLED and returns immediately without blocking."}`;
|
|
27
27
|
const execute: ToolDefinition<typeof SubagentWaitParams, Details>["execute"] = async (_id, params, signal, onUpdate, ctx) => finalizeToolResult(await waitForSubagents(params, signal, {
|
|
28
28
|
state,
|
|
29
29
|
events: pi.events,
|
|
@@ -40,10 +40,4 @@ Non-blocking subscriptions are visible in subagent status and differ from disabl
|
|
|
40
40
|
execute,
|
|
41
41
|
};
|
|
42
42
|
pi.registerTool(primaryTool);
|
|
43
|
-
pi.registerTool({
|
|
44
|
-
...primaryTool,
|
|
45
|
-
name: "subagent_wait",
|
|
46
|
-
label: "Subagent Wait (deprecated)",
|
|
47
|
-
description: "Deprecated compatibility alias for `bg_wait`. Use `bg_wait` for background, provider, or detached work without a native completion notification. It has the same parameters and behavior.",
|
|
48
|
-
});
|
|
49
43
|
}
|