pi-subagents 0.83.1 → 0.84.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 +17 -0
- package/bundle/{chunk-5YIITCPD.js → chunk-235AWJUQ.js} +24 -21
- package/bundle/{chunk-YXFJVCOL.js → chunk-4E7BKDTX.js} +3 -3
- package/bundle/{chunk-UFL7IYCW.js → chunk-7BWGNDNK.js} +2 -2
- package/bundle/{chunk-ZVOAMTUQ.js → chunk-AKOIVLA5.js} +3 -3
- package/bundle/{chunk-RNIZLSEA.js → chunk-CGN2TVE7.js} +1 -0
- package/bundle/{chunk-F5DBXJ2G.js → chunk-DZDETGRJ.js} +8 -5
- package/bundle/{chunk-C7JMPU5F.js → chunk-J5YL7LYR.js} +1 -1
- package/bundle/{chunk-UUP372DR.js → chunk-JG4QKI5M.js} +22 -8
- package/bundle/{chunk-UYK5MZWE.js → chunk-L3WFKBEI.js} +1 -1
- package/bundle/{chunk-VLFZIN6D.js → chunk-NENDSQUY.js} +2 -2
- package/bundle/{chunk-MABL3FUM.js → chunk-NKQ6JNAS.js} +1 -1
- package/bundle/{chunk-QP5ZWNIS.js → chunk-PALJGGYC.js} +1 -1
- package/bundle/{chunk-IPGZMQXV.js → chunk-PV63UFUR.js} +5 -5
- package/bundle/{chunk-AHIFYGR3.js → chunk-VQVVHSY6.js} +5 -5
- package/bundle/{chunk-NPIYWVOD.js → chunk-XDQORLZ5.js} +3 -3
- package/bundle/index.js +1 -1
- package/bundle/preflight.js +2 -2
- package/package.json +1 -1
- package/src/runs/background/async-execution.d.ts +3 -0
- package/src/runs/background/async-execution.js +15 -0
- package/src/runs/background/async-retention.d.ts +1 -0
- package/src/runs/background/async-retention.js +16 -10
- package/src/runs/background/run-child-session.d.ts +2 -0
- package/src/runs/background/run-child-session.js +9 -1
- package/src/runs/background/subagent-runner.js +1 -0
- package/src/runs/foreground/subagent-executor.js +5 -0
- package/src/runs/shared/child-session.d.ts +2 -0
- package/src/runs/shared/child-session.js +1 -0
- package/src/runs/shared/model-resolution-diagnostic.d.ts +11 -0
- package/src/runs/shared/model-resolution-diagnostic.js +25 -0
- package/src/runs/shared/parallel-utils.d.ts +2 -0
- package/src/workflows/scripted-workflow.js +2 -0
- package/workflow-script-worker.cjs +449 -175
package/package.json
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Async execution logic for subagent tool
|
|
3
3
|
*/
|
|
4
4
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import type { ParentProviderRegistry } from "../shared/child-session.ts";
|
|
5
6
|
import { type AgentConfig, type UnknownAgentDiagnosticContext } from "../../agents/agents.ts";
|
|
6
7
|
import { type ClaudeCodeOverride } from "../shared/claude-code-adapter.ts";
|
|
7
8
|
import { type ChainStep } from "../../shared/settings.ts";
|
|
@@ -44,6 +45,8 @@ interface AsyncExecutionContext {
|
|
|
44
45
|
childRuntime?: ChildRuntimeConfig;
|
|
45
46
|
/** The launching session's project trust; undefined when the host has no trust concept. */
|
|
46
47
|
projectTrusted?: boolean;
|
|
48
|
+
/** The parent's model registry, read at launch to name a provider an extension registered there. */
|
|
49
|
+
parentProviderRegistry?: Pick<ParentProviderRegistry, "getRegisteredProviderIds">;
|
|
47
50
|
}
|
|
48
51
|
export declare const DEFAULT_ASYNC_TIMEOUT_MS: number;
|
|
49
52
|
interface AsyncChainParams {
|
|
@@ -135,6 +135,17 @@ function supportsNativeRunner(nodeExecutable) {
|
|
|
135
135
|
&& nodeExecutable === process.execPath
|
|
136
136
|
&& !sourceUnderNodeModules();
|
|
137
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* A runner child without the ambient extensions does not load the parent extension that
|
|
140
|
+
* registered its model's provider, and still resolves the model when a built-in provider
|
|
141
|
+
* has the same id. Name that provider so a failed run can say so.
|
|
142
|
+
*/
|
|
143
|
+
function parentExtensionProviderForChild(ctx, disableAmbientExtensions, model, availableModels, preferredProvider) {
|
|
144
|
+
if (!disableAmbientExtensions || !ctx.parentProviderRegistry)
|
|
145
|
+
return undefined;
|
|
146
|
+
const provider = findModelInfo(model, availableModels, preferredProvider)?.provider;
|
|
147
|
+
return provider && ctx.parentProviderRegistry.getRegisteredProviderIds().includes(provider) ? provider : undefined;
|
|
148
|
+
}
|
|
138
149
|
export const DEFAULT_ASYNC_TIMEOUT_MS = 30 * 60 * 1000;
|
|
139
150
|
export function formatAsyncStartedMessage(headline, interactive) {
|
|
140
151
|
const guidance = interactive
|
|
@@ -997,6 +1008,7 @@ export function buildAsyncRunnerSteps(id, params) {
|
|
|
997
1008
|
runtimeSnapshotHost: ctx.pi,
|
|
998
1009
|
});
|
|
999
1010
|
const launchResolvedExtensions = externalRunner ? undefined : projectLaunchResolvedChildExtensions(toolPlan);
|
|
1011
|
+
const parentExtensionProvider = externalRunner || machine ? undefined : parentExtensionProviderForChild(ctx, toolPlan.disableAmbientExtensions, selectedModel, availableModels, a.modelProvider ?? ctx.currentModelProvider);
|
|
1000
1012
|
if (externalRunner && permissionRules) {
|
|
1001
1013
|
throw new AsyncStartValidationError(`Agent '${a.name}' uses runner.type='${externalRunnerType}', which cannot enforce native Pi child permission rules.`);
|
|
1002
1014
|
}
|
|
@@ -1026,6 +1038,7 @@ export function buildAsyncRunnerSteps(id, params) {
|
|
|
1026
1038
|
thinking: resolveEffectiveThinking(selectedModel, effectiveThinking),
|
|
1027
1039
|
...(thinkingCeiling ? { thinkingCeiling } : {}),
|
|
1028
1040
|
launchResolvedExtensions,
|
|
1041
|
+
...(parentExtensionProvider ? { parentExtensionProvider } : {}),
|
|
1029
1042
|
...(requestedModel ? { requestedModel } : {}),
|
|
1030
1043
|
...(primaryModelFromParent ? { skipPrimaryModelVerification: true } : {}),
|
|
1031
1044
|
...(availableModels && availableModels.length > 0 ? { modelVerificationRegistry: availableModels } : {}),
|
|
@@ -1811,6 +1824,7 @@ export function executeAsyncSingle(id, params) {
|
|
|
1811
1824
|
runtimeSnapshotHost: ctx.pi,
|
|
1812
1825
|
});
|
|
1813
1826
|
const launchResolvedExtensions = externalRunner ? undefined : projectLaunchResolvedChildExtensions(toolPlan);
|
|
1827
|
+
const parentExtensionProvider = externalRunner || machine ? undefined : parentExtensionProviderForChild(ctx, toolPlan.disableAmbientExtensions, selectedModel, availableModels, agentConfig.modelProvider ?? ctx.currentModelProvider);
|
|
1814
1828
|
const fast = params.fast ?? agentConfig.fast;
|
|
1815
1829
|
const launchThinking = resolveEffectiveThinking(selectedModel, effectiveThinking);
|
|
1816
1830
|
const { definitionDigest, launchContractDigest } = resolveLaunchBinding({
|
|
@@ -1933,6 +1947,7 @@ export function executeAsyncSingle(id, params) {
|
|
|
1933
1947
|
...(params.fast ?? agentConfig.fast ? { fast: params.fast ?? agentConfig.fast } : {}),
|
|
1934
1948
|
thinking: resolveEffectiveThinking(selectedModel, effectiveThinking),
|
|
1935
1949
|
...(thinkingCeiling ? { thinkingCeiling } : {}),
|
|
1950
|
+
...(parentExtensionProvider ? { parentExtensionProvider } : {}),
|
|
1936
1951
|
...(requestedModel ? { requestedModel } : {}),
|
|
1937
1952
|
...(modelOrigin === "inherited" ? { skipPrimaryModelVerification: true } : {}),
|
|
1938
1953
|
...(availableModels && availableModels.length > 0 ? { modelVerificationRegistry: availableModels } : {}),
|
|
@@ -3,6 +3,7 @@ export declare const ASYNC_RETENTION_DAYS = 30;
|
|
|
3
3
|
export declare const ASYNC_RETENTION_BATCH_SIZE = 100;
|
|
4
4
|
export declare const ASYNC_RETENTION_DELAY_MS = 60000;
|
|
5
5
|
export declare const ASYNC_RETENTION_TOMBSTONE_GRACE_MS: number;
|
|
6
|
+
export declare const RESUMABLE_RETENTION_DAYS = 90;
|
|
6
7
|
export interface AsyncRetentionOptions {
|
|
7
8
|
asyncDirRoot: string;
|
|
8
9
|
resultsDir: string;
|
|
@@ -15,7 +15,11 @@ export const ASYNC_RETENTION_DAYS = 30;
|
|
|
15
15
|
export const ASYNC_RETENTION_BATCH_SIZE = 100;
|
|
16
16
|
export const ASYNC_RETENTION_DELAY_MS = 60_000;
|
|
17
17
|
export const ASYNC_RETENTION_TOMBSTONE_GRACE_MS = 24 * 60 * 60 * 1000;
|
|
18
|
+
// A run or result whose session file still exists can be resumed, so it is kept this long
|
|
19
|
+
// instead of ASYNC_RETENTION_DAYS. Session files outlive run dirs, so without a ceiling it is kept forever.
|
|
20
|
+
export const RESUMABLE_RETENTION_DAYS = 90;
|
|
18
21
|
const RETENTION_MS = ASYNC_RETENTION_DAYS * 24 * 60 * 60 * 1000;
|
|
22
|
+
const RESUMABLE_RETENTION_MS = RESUMABLE_RETENTION_DAYS * 24 * 60 * 60 * 1000;
|
|
19
23
|
const LOCK_NAME = ".async-retention.lock";
|
|
20
24
|
const CURSOR_NAME = ".async-retention-cursor.json";
|
|
21
25
|
const LOG_NAME = "async-retention-maintenance.jsonl";
|
|
@@ -239,9 +243,10 @@ function runSkipReason(input) {
|
|
|
239
243
|
return "mission-reference";
|
|
240
244
|
if (hasUnresolvedRunHandoff(runDir, status))
|
|
241
245
|
return "handoff-reference";
|
|
242
|
-
if (hasResumableContract(runDir, status))
|
|
243
|
-
return "resumable";
|
|
244
246
|
const timestamp = statusTimestamp(status, runDir);
|
|
247
|
+
// Unknown ages stay protected.
|
|
248
|
+
if (hasResumableContract(runDir, status) && (timestamp === undefined || timestamp > input.resumableCutoff))
|
|
249
|
+
return "resumable";
|
|
245
250
|
if (timestamp === undefined)
|
|
246
251
|
return "unknown-age";
|
|
247
252
|
if (timestamp > input.cutoff)
|
|
@@ -464,13 +469,13 @@ function resultSkipReason(input) {
|
|
|
464
469
|
return "workflow-reference";
|
|
465
470
|
if (hasUnresolvedResultHandoff(input.data))
|
|
466
471
|
return "handoff-reference";
|
|
467
|
-
|
|
472
|
+
const timestamp = resultTimestamp(input.data, input.candidate.path);
|
|
473
|
+
if (resultHasResumableSession(input.data) && (timestamp === undefined || timestamp > input.resumableCutoff))
|
|
468
474
|
return "resumable";
|
|
469
475
|
if (input.candidate.kind === "archive" && fs.existsSync(path.join(input.resultsDir, "completion-replay", `${encodeURIComponent(runId)}.json`)))
|
|
470
476
|
return "replay-reference";
|
|
471
477
|
if (!terminalResult(input.data) && input.candidate.kind !== "replay" && input.candidate.kind !== "archive" && input.candidate.kind !== "tombstone")
|
|
472
478
|
return "non-terminal-result";
|
|
473
|
-
const timestamp = resultTimestamp(input.data, input.candidate.path);
|
|
474
479
|
if (timestamp === undefined)
|
|
475
480
|
return "unknown-age";
|
|
476
481
|
if (timestamp > input.cutoff)
|
|
@@ -690,6 +695,7 @@ export async function cleanupAsyncRetention(options) {
|
|
|
690
695
|
return finish();
|
|
691
696
|
const protectedRunIds = new Set(options.protectedRunIds ?? []);
|
|
692
697
|
const cutoff = currentTime - retentionMs;
|
|
698
|
+
const resumableCutoff = currentTime - RESUMABLE_RETENTION_MS;
|
|
693
699
|
const cursor = readCursor(maintenanceRoot);
|
|
694
700
|
const runBudget = Math.ceil(batchSize / 2);
|
|
695
701
|
const resultBudget = batchSize - runBudget;
|
|
@@ -765,7 +771,7 @@ export async function cleanupAsyncRetention(options) {
|
|
|
765
771
|
if (reconciliation.repaired)
|
|
766
772
|
result.repairedRuns += 1;
|
|
767
773
|
}
|
|
768
|
-
const initialReason = runSkipReason({ runDir, status, asyncDirRoot: options.asyncDirRoot, resultsDir: options.resultsDir, cutoff, protectedRunIds, waitRunIds: waitReferences.runIds });
|
|
774
|
+
const initialReason = runSkipReason({ runDir, status, asyncDirRoot: options.asyncDirRoot, resultsDir: options.resultsDir, cutoff, resumableCutoff, protectedRunIds, waitRunIds: waitReferences.runIds });
|
|
769
775
|
if (initialReason) {
|
|
770
776
|
increment(result.skipped, initialReason);
|
|
771
777
|
continue;
|
|
@@ -780,7 +786,7 @@ export async function cleanupAsyncRetention(options) {
|
|
|
780
786
|
continue;
|
|
781
787
|
}
|
|
782
788
|
const freshWaitReferences = parseWaitRunIds(waitSubscriptionsDir);
|
|
783
|
-
const finalReason = freshWaitReferences.safe ? runSkipReason({ runDir, status, asyncDirRoot: options.asyncDirRoot, resultsDir: options.resultsDir, cutoff, protectedRunIds, waitRunIds: freshWaitReferences.runIds }) : "wait-references-unknown";
|
|
789
|
+
const finalReason = freshWaitReferences.safe ? runSkipReason({ runDir, status, asyncDirRoot: options.asyncDirRoot, resultsDir: options.resultsDir, cutoff, resumableCutoff, protectedRunIds, waitRunIds: freshWaitReferences.runIds }) : "wait-references-unknown";
|
|
784
790
|
if (finalReason) {
|
|
785
791
|
increment(result.skipped, `recheck-${finalReason}`);
|
|
786
792
|
continue;
|
|
@@ -805,7 +811,7 @@ export async function cleanupAsyncRetention(options) {
|
|
|
805
811
|
}
|
|
806
812
|
const recheckStatus = readStatus(tombstone);
|
|
807
813
|
const freshWaitReferences = parseWaitRunIds(waitSubscriptionsDir);
|
|
808
|
-
const recheckReason = freshWaitReferences.safe ? runSkipReason({ runDir: tombstone, status: recheckStatus, asyncDirRoot: options.asyncDirRoot, resultsDir: options.resultsDir, cutoff, protectedRunIds, waitRunIds: freshWaitReferences.runIds }) : "wait-references-unknown";
|
|
814
|
+
const recheckReason = freshWaitReferences.safe ? runSkipReason({ runDir: tombstone, status: recheckStatus, asyncDirRoot: options.asyncDirRoot, resultsDir: options.resultsDir, cutoff, resumableCutoff, protectedRunIds, waitRunIds: freshWaitReferences.runIds }) : "wait-references-unknown";
|
|
809
815
|
if (recheckReason) {
|
|
810
816
|
if (!fs.existsSync(runDir)) {
|
|
811
817
|
fs.renameSync(tombstone, runDir);
|
|
@@ -842,7 +848,7 @@ export async function cleanupAsyncRetention(options) {
|
|
|
842
848
|
continue;
|
|
843
849
|
}
|
|
844
850
|
const data = readJson(candidate.path);
|
|
845
|
-
const reason = resultSkipReason({ candidate, data, asyncDirRoot: options.asyncDirRoot, resultsDir: options.resultsDir, maintenanceRoot, cutoff, protectedRunIds, waitRunIds: waitReferences.runIds });
|
|
851
|
+
const reason = resultSkipReason({ candidate, data, asyncDirRoot: options.asyncDirRoot, resultsDir: options.resultsDir, maintenanceRoot, cutoff, resumableCutoff, protectedRunIds, waitRunIds: waitReferences.runIds });
|
|
846
852
|
if (reason) {
|
|
847
853
|
increment(result.skipped, reason);
|
|
848
854
|
continue;
|
|
@@ -853,7 +859,7 @@ export async function cleanupAsyncRetention(options) {
|
|
|
853
859
|
continue;
|
|
854
860
|
}
|
|
855
861
|
const freshWaitReferences = parseWaitRunIds(waitSubscriptionsDir);
|
|
856
|
-
const finalReason = freshWaitReferences.safe ? resultSkipReason({ candidate, data, asyncDirRoot: options.asyncDirRoot, resultsDir: options.resultsDir, maintenanceRoot, cutoff, protectedRunIds, waitRunIds: freshWaitReferences.runIds }) : "wait-references-unknown";
|
|
862
|
+
const finalReason = freshWaitReferences.safe ? resultSkipReason({ candidate, data, asyncDirRoot: options.asyncDirRoot, resultsDir: options.resultsDir, maintenanceRoot, cutoff, resumableCutoff, protectedRunIds, waitRunIds: freshWaitReferences.runIds }) : "wait-references-unknown";
|
|
857
863
|
if (finalReason) {
|
|
858
864
|
increment(result.skipped, `recheck-${finalReason}`);
|
|
859
865
|
continue;
|
|
@@ -870,7 +876,7 @@ export async function cleanupAsyncRetention(options) {
|
|
|
870
876
|
const tombstoneCandidate = { ...candidate, path: tombstone, kind: "tombstone" };
|
|
871
877
|
const recheckData = readJson(tombstone);
|
|
872
878
|
const freshWaitReferences = parseWaitRunIds(waitSubscriptionsDir);
|
|
873
|
-
const recheckReason = freshWaitReferences.safe ? resultSkipReason({ candidate: tombstoneCandidate, data: recheckData, asyncDirRoot: options.asyncDirRoot, resultsDir: options.resultsDir, maintenanceRoot, cutoff, protectedRunIds, waitRunIds: freshWaitReferences.runIds }) : "wait-references-unknown";
|
|
879
|
+
const recheckReason = freshWaitReferences.safe ? resultSkipReason({ candidate: tombstoneCandidate, data: recheckData, asyncDirRoot: options.asyncDirRoot, resultsDir: options.resultsDir, maintenanceRoot, cutoff, resumableCutoff, protectedRunIds, waitRunIds: freshWaitReferences.runIds }) : "wait-references-unknown";
|
|
874
880
|
if (recheckReason) {
|
|
875
881
|
if (!fs.existsSync(candidate.path)) {
|
|
876
882
|
fs.renameSync(tombstone, candidate.path);
|
|
@@ -83,6 +83,8 @@ export interface RunChildSessionInput {
|
|
|
83
83
|
}>;
|
|
84
84
|
modelResponseAliases?: Record<string, string[]>;
|
|
85
85
|
mutationTools?: readonly string[];
|
|
86
|
+
/** Provider of the launch model that a parent extension registered and this child does not load. */
|
|
87
|
+
parentExtensionProvider?: string;
|
|
86
88
|
}
|
|
87
89
|
export interface RunChildSessionResult {
|
|
88
90
|
exitCode: number;
|
|
@@ -3,7 +3,7 @@ import { qualifyModelWithProvider } from "../../shared/model-info.js";
|
|
|
3
3
|
import { acceptChildWatchdogEvent, applyChildWatchdogMessage, childWatchdogIsActive, isChildWatchdogStatusEvent, } from "../../watchdog/child-status.js";
|
|
4
4
|
import { projectChildLifecycle } from "../shared/child-lifecycle.js";
|
|
5
5
|
import { formatSubagentModelVerificationError } from "../shared/model-resolution.js";
|
|
6
|
-
import { formatChildModelResolutionDiagnostic, isChildModelResolutionFailure } from "../shared/model-resolution-diagnostic.js";
|
|
6
|
+
import { formatChildModelResolutionDiagnostic, formatMissingParentProviderDiagnostic, isChildModelResolutionFailure } from "../shared/model-resolution-diagnostic.js";
|
|
7
7
|
import { isMutatingTool, resolveCurrentPath } from "../shared/long-running-guard.js";
|
|
8
8
|
import { effectiveToolTimeoutMs, formatToolTimeoutMessage, toolTimeoutCallKey } from "../shared/tool-timeout.js";
|
|
9
9
|
import { createReportedChildSessionInput } from "../shared/child-launch.js";
|
|
@@ -520,6 +520,14 @@ export function runChildSession(input) {
|
|
|
520
520
|
&& !input.launch.session.ambientExtensions) {
|
|
521
521
|
finalError = `${promptErrorMessage}\n\n${formatChildModelResolutionDiagnostic({ agent: input.launch.config.agent, model: input.launch.session.model, host: "runner", capabilityCeiling: input.launch.toolPlan.capabilityCeiling })}`;
|
|
522
522
|
}
|
|
523
|
+
else if (input.parentExtensionProvider
|
|
524
|
+
&& session?.claimedProvider !== input.parentExtensionProvider
|
|
525
|
+
&& finalError !== undefined
|
|
526
|
+
&& (finalError === promptErrorMessage || finalError === assistantError)
|
|
527
|
+
&& toolCount === 0
|
|
528
|
+
&& messages.every((message) => message.role !== "assistant" || message.stopReason === "error")) {
|
|
529
|
+
finalError = `${finalError}\n\n${formatMissingParentProviderDiagnostic({ agent: input.launch.config.agent, model: input.launch.session.model, provider: input.parentExtensionProvider, capabilityCeiling: input.launch.toolPlan.capabilityCeiling })}`;
|
|
530
|
+
}
|
|
523
531
|
const forcedDrainAfterFinalSuccess = (forced || forcedTermination) && (cleanTerminalAssistantStopReceived || agentSettledReceived) && !finalError;
|
|
524
532
|
const forcedDrainAfterEmptyTerminal = forcedDrainAfterFinalSuccess && hasEmptyTerminalAssistantResponse(messages);
|
|
525
533
|
if (!finalError && forced && !forcedDrainAfterFinalSuccess && !interrupted && !timedOut && !stopped) {
|
|
@@ -938,6 +938,7 @@ export async function runSingleStepInner(step, ctx) {
|
|
|
938
938
|
modelVerificationRegistry: step.modelVerificationRegistry,
|
|
939
939
|
modelResponseAliases: step.modelResponseAliases,
|
|
940
940
|
mutationTools: step.mutationTools,
|
|
941
|
+
parentExtensionProvider: step.parentExtensionProvider,
|
|
941
942
|
}));
|
|
942
943
|
launched = true;
|
|
943
944
|
aggregateUsage.input += run.usage.input;
|
|
@@ -1018,6 +1018,7 @@ function appendStepToAsyncChain(input) {
|
|
|
1018
1018
|
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
1019
1019
|
parentSessionFile: input.ctx.sessionManager.getSessionFile() ?? undefined,
|
|
1020
1020
|
currentModelProvider: parentModel?.provider,
|
|
1021
|
+
parentProviderRegistry: input.ctx.modelRegistry,
|
|
1021
1022
|
currentModel: parentModel,
|
|
1022
1023
|
scopedModelIds: scopedModelIdsFromContext(input.ctx),
|
|
1023
1024
|
modelScope: discoveredForAppend.modelScope,
|
|
@@ -1505,6 +1506,7 @@ async function resumeExternalJobFollowUp(input) {
|
|
|
1505
1506
|
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
1506
1507
|
parentSessionFile: input.ctx.sessionManager.getSessionFile() ?? undefined,
|
|
1507
1508
|
currentModelProvider: parentModel?.provider,
|
|
1509
|
+
parentProviderRegistry: input.ctx.modelRegistry,
|
|
1508
1510
|
currentModel: parentModel,
|
|
1509
1511
|
scopedModelIds: scopedModelIdsFromContext(input.ctx),
|
|
1510
1512
|
modelScope: input.modelScope,
|
|
@@ -1793,6 +1795,7 @@ async function resumeAsyncRun(input) {
|
|
|
1793
1795
|
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
1794
1796
|
parentSessionFile: input.ctx.sessionManager.getSessionFile() ?? undefined,
|
|
1795
1797
|
currentModelProvider: parentModel?.provider,
|
|
1798
|
+
parentProviderRegistry: input.ctx.modelRegistry,
|
|
1796
1799
|
currentModel: parentModel,
|
|
1797
1800
|
scopedModelIds: scopedModelIdsFromContext(input.ctx),
|
|
1798
1801
|
modelScope,
|
|
@@ -1934,6 +1937,7 @@ async function resumeAsyncRun(input) {
|
|
|
1934
1937
|
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
1935
1938
|
parentSessionFile: input.ctx.sessionManager.getSessionFile() ?? undefined,
|
|
1936
1939
|
currentModelProvider: parentModel?.provider,
|
|
1940
|
+
parentProviderRegistry: input.ctx.modelRegistry,
|
|
1937
1941
|
currentModel: parentModel,
|
|
1938
1942
|
scopedModelIds: scopedModelIdsFromContext(input.ctx),
|
|
1939
1943
|
modelScope,
|
|
@@ -3105,6 +3109,7 @@ async function runAsyncPath(data, deps) {
|
|
|
3105
3109
|
parentSessionId: data.parentPiSessionId,
|
|
3106
3110
|
parentSessionFile: ctx.sessionManager.getSessionFile() ?? undefined,
|
|
3107
3111
|
currentModelProvider: parentModel?.provider,
|
|
3112
|
+
parentProviderRegistry: ctx.modelRegistry,
|
|
3108
3113
|
currentModel: parentModel,
|
|
3109
3114
|
scopedModelIds: data.scopedModelIds,
|
|
3110
3115
|
modelScope: data.modelScope,
|
|
@@ -109,6 +109,8 @@ export interface ChildSession {
|
|
|
109
109
|
readonly modelId: string | undefined;
|
|
110
110
|
/** Provider id of the selected model when this child inherited that provider from the parent's registry instead of loading its extension. */
|
|
111
111
|
readonly inheritedProvider?: string;
|
|
112
|
+
/** Provider id of the selected model when an extension in this child's model runtime registered that provider, at load or later (e.g. on `session_start`). */
|
|
113
|
+
readonly claimedProvider?: string;
|
|
112
114
|
/** Live provider/id of the selected model when Pi marks it virtual (`api === "pi-virtual"`); assistant messages then name the dispatched physical model. */
|
|
113
115
|
readonly virtualModelId?: string;
|
|
114
116
|
readonly contextWindow?: number;
|
|
@@ -633,6 +633,7 @@ export function createDefaultChildSessionFactory(options = {}) {
|
|
|
633
633
|
get sessionId() { return session.sessionId; },
|
|
634
634
|
get modelId() { return session.model ? `${session.model.provider}/${session.model.id}` : undefined; },
|
|
635
635
|
get inheritedProvider() { return session.model && inheritedProviders.has(session.model.provider) ? session.model.provider : undefined; },
|
|
636
|
+
get claimedProvider() { return session.model && !inheritedProviders.has(session.model.provider) && modelRuntime.getRegisteredProviderIds().includes(session.model.provider) ? session.model.provider : undefined; },
|
|
636
637
|
get virtualModelId() { return session.model?.api === "pi-virtual" ? `${session.model.provider}/${session.model.id}` : undefined; },
|
|
637
638
|
get contextWindow() { return session.model?.contextWindow; },
|
|
638
639
|
};
|
|
@@ -31,3 +31,14 @@ export declare function formatChildModelResolutionDiagnostic(diagnostic: ChildMo
|
|
|
31
31
|
export declare function formatInheritedProviderDiagnostic(diagnostic: ChildModelResolutionDiagnostic & {
|
|
32
32
|
provider: string;
|
|
33
33
|
}): string;
|
|
34
|
+
/**
|
|
35
|
+
* Explain a background child that failed before any successful response on a
|
|
36
|
+
* provider a parent extension registered, when that extension did not load in
|
|
37
|
+
* the child. A parent extension can register a provider under a built-in id,
|
|
38
|
+
* so the model still resolves in the child, through the built-in provider
|
|
39
|
+
* rather than the parent's. Whether that caused this failure is unknown here,
|
|
40
|
+
* hence the conditional wording.
|
|
41
|
+
*/
|
|
42
|
+
export declare function formatMissingParentProviderDiagnostic(diagnostic: ChildModelResolutionDiagnostic & {
|
|
43
|
+
provider: string;
|
|
44
|
+
}): string;
|
|
@@ -89,3 +89,28 @@ export function formatInheritedProviderDiagnostic(diagnostic) {
|
|
|
89
89
|
`If ${provider} depends on those hooks, run this agent with \`async: true\` (a background child loads the ambient extensions unless the agent sets \`extensions\`), or load the extension for this agent with \`subagentOnlyExtensions\` or \`extensions\` in the agent frontmatter.`,
|
|
90
90
|
].join("\n");
|
|
91
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Explain a background child that failed before any successful response on a
|
|
94
|
+
* provider a parent extension registered, when that extension did not load in
|
|
95
|
+
* the child. A parent extension can register a provider under a built-in id,
|
|
96
|
+
* so the model still resolves in the child, through the built-in provider
|
|
97
|
+
* rather than the parent's. Whether that caused this failure is unknown here,
|
|
98
|
+
* hence the conditional wording.
|
|
99
|
+
*/
|
|
100
|
+
export function formatMissingParentProviderDiagnostic(diagnostic) {
|
|
101
|
+
const subject = diagnostic.agent ? `Agent '${diagnostic.agent}'` : "Subagent";
|
|
102
|
+
const model = diagnostic.model ? `'${diagnostic.model}'` : "this model";
|
|
103
|
+
const provider = `'${diagnostic.provider}'`;
|
|
104
|
+
const facts = `${subject} ran as a background child without the ambient extensions. In the parent, provider ${provider} for ${model} is registered by an extension that did not load in this child, so the child sent its requests without that extension's provider, through the built-in provider of the same id if there is one.`;
|
|
105
|
+
if (capabilityCeilingDeniesExtensions(diagnostic)) {
|
|
106
|
+
return [
|
|
107
|
+
facts,
|
|
108
|
+
`${denyExtensionsClause(diagnostic)}, so that extension cannot load for this child: listed \`subagentOnlyExtensions\` or \`extensions\` entries are suppressed, and leaving \`extensions\` unset does not enable ambient loading.`,
|
|
109
|
+
`If ${provider} depends on that extension, relax the capability ceiling to allow extensions, or use a model whose provider does not.`,
|
|
110
|
+
].join("\n");
|
|
111
|
+
}
|
|
112
|
+
return [
|
|
113
|
+
facts,
|
|
114
|
+
`If ${provider} depends on that extension, list it in \`subagentOnlyExtensions\` or \`extensions\` in the agent frontmatter, leave \`extensions\` unset so the child loads the ambient extensions, or register it from the host with \`registerRequiredChildExtensions\` so it loads for every launch.`,
|
|
115
|
+
].join("\n");
|
|
116
|
+
}
|
|
@@ -55,6 +55,8 @@ export interface RunnerSubagentStep {
|
|
|
55
55
|
}>;
|
|
56
56
|
modelResponseAliases?: Record<string, string[]>;
|
|
57
57
|
tools?: string[];
|
|
58
|
+
/** Provider of `model` that a parent extension registers; set only when this child loads no ambient extensions. */
|
|
59
|
+
parentExtensionProvider?: string;
|
|
58
60
|
excludeTools?: string[];
|
|
59
61
|
allowNestedSubagents?: boolean;
|
|
60
62
|
/** Resolved selected-agent policy for launches made by this child. */
|
|
@@ -780,6 +780,8 @@ export function validateWorkflowScript(script, options = {}) {
|
|
|
780
780
|
if (node !== wrapper && node.async === true && (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression")) {
|
|
781
781
|
errors.push({ message: "workflowScript does not support nested async functions. Use top-level await, plain helper functions that return runs.run(...), or explicit Promise chains.", ...nodeLocation(node) });
|
|
782
782
|
}
|
|
783
|
+
if (node.type === "ImportExpression")
|
|
784
|
+
errors.push({ message: "workflowScript cannot load modules with import().", ...nodeLocation(node) });
|
|
783
785
|
if (directRunsCall(node, "run")) {
|
|
784
786
|
const args = Array.isArray(node.arguments) ? node.arguments : [];
|
|
785
787
|
const keyNode = astNode(args[0]) ? args[0] : undefined;
|