@bastani/atomic 0.8.29 → 0.8.30-alpha.1
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 +25 -0
- package/dist/builtin/cursor/CHANGELOG.md +4 -0
- package/dist/builtin/cursor/package.json +2 -2
- package/dist/builtin/intercom/CHANGELOG.md +4 -0
- package/dist/builtin/intercom/package.json +2 -2
- package/dist/builtin/mcp/CHANGELOG.md +4 -0
- package/dist/builtin/mcp/package.json +3 -3
- package/dist/builtin/subagents/CHANGELOG.md +9 -0
- package/dist/builtin/subagents/README.md +10 -30
- package/dist/builtin/subagents/package.json +4 -4
- package/dist/builtin/subagents/skills/subagent/SKILL.md +5 -11
- package/dist/builtin/subagents/src/agents/agent-management.ts +0 -5
- package/dist/builtin/subagents/src/agents/agent-serializer.ts +7 -3
- package/dist/builtin/subagents/src/agents/agents.ts +4 -29
- package/dist/builtin/subagents/src/agents/chain-serializer.ts +27 -25
- package/dist/builtin/subagents/src/extension/schemas.ts +0 -75
- package/dist/builtin/subagents/src/runs/background/async-execution.ts +0 -29
- package/dist/builtin/subagents/src/runs/background/run-status.ts +1 -2
- package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +134 -239
- package/dist/builtin/subagents/src/runs/foreground/chain-execution.ts +1 -52
- package/dist/builtin/subagents/src/runs/foreground/execution.ts +103 -94
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +0 -10
- package/dist/builtin/subagents/src/runs/shared/dynamic-fanout.ts +16 -8
- package/dist/builtin/subagents/src/runs/shared/model-fallback.ts +0 -1
- package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +0 -3
- package/dist/builtin/subagents/src/runs/shared/structured-output.ts +67 -2
- package/dist/builtin/subagents/src/runs/shared/subagent-control.ts +6 -20
- package/dist/builtin/subagents/src/runs/shared/subagent-prompt-runtime.ts +1 -1
- package/dist/builtin/subagents/src/runs/shared/workflow-graph.ts +2 -6
- package/dist/builtin/subagents/src/shared/settings.ts +1 -4
- package/dist/builtin/subagents/src/shared/types.ts +1 -156
- package/dist/builtin/subagents/src/tui/render.ts +0 -1
- package/dist/builtin/web-access/CHANGELOG.md +4 -0
- package/dist/builtin/web-access/package.json +2 -2
- package/dist/builtin/workflows/CHANGELOG.md +11 -0
- package/dist/builtin/workflows/README.md +2 -2
- package/dist/builtin/workflows/package.json +2 -2
- package/dist/builtin/workflows/src/extension/index.ts +8 -1
- package/dist/builtin/workflows/src/extension/wiring.ts +66 -10
- package/dist/builtin/workflows/src/runs/foreground/executor.ts +70 -19
- package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +98 -14
- package/dist/builtin/workflows/src/runs/shared/model-fallback.ts +0 -1
- package/dist/builtin/workflows/src/shared/persistence-restore.ts +4 -0
- package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +4 -0
- package/dist/builtin/workflows/src/shared/store.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +18 -4
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +2 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +21 -9
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +2 -2
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +6 -0
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +2 -0
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/compaction/context-compaction.d.ts +43 -16
- package/dist/core/compaction/context-compaction.d.ts.map +1 -1
- package/dist/core/compaction/context-compaction.js +518 -189
- package/dist/core/compaction/context-compaction.js.map +1 -1
- package/dist/core/extensions/loader.d.ts +4 -2
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +11 -7
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/types.d.ts +14 -3
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +2 -1
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/package-manager.d.ts +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +52 -18
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/resource-loader.d.ts +20 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +89 -24
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/session-manager.d.ts +14 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +145 -3
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +9 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +16 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/thinking-blocks.d.ts +7 -0
- package/dist/core/thinking-blocks.d.ts.map +1 -0
- package/dist/core/thinking-blocks.js +16 -0
- package/dist/core/thinking-blocks.js.map +1 -0
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +4 -0
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +30 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +87 -12
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +37 -18
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts +24 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +58 -13
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/utils/child-process.d.ts +9 -4
- package/dist/utils/child-process.d.ts.map +1 -1
- package/dist/utils/child-process.js +42 -10
- package/dist/utils/child-process.js.map +1 -1
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +4 -27
- package/dist/utils/version-check.js.map +1 -1
- package/docs/compaction.md +469 -51
- package/docs/containerization.md +37 -37
- package/docs/extensions.md +23 -14
- package/docs/models.md +6 -4
- package/docs/packages.md +2 -0
- package/docs/providers.md +1 -1
- package/docs/subagents.md +11 -4
- package/docs/workflows.md +4 -2
- package/examples/extensions/README.md +2 -2
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/question.ts +39 -18
- package/examples/extensions/questionnaire.ts +49 -28
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +7 -5
- package/dist/builtin/subagents/src/runs/shared/acceptance.ts +0 -612
- package/dist/builtin/subagents/src/runs/shared/completion-guard.ts +0 -147
|
@@ -66,7 +66,6 @@ import { buildWorkflowGraphSnapshot } from "../shared/workflow-graph.ts";
|
|
|
66
66
|
import { ChainOutputValidationError, outputEntryFromResult, resolveOutputReferences, validateChainOutputBindings } from "../shared/chain-outputs.ts";
|
|
67
67
|
import { createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
68
68
|
import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelStep, validateDynamicCollection, type DynamicCollectedResult } from "../shared/dynamic-fanout.ts";
|
|
69
|
-
import { acceptanceFailureMessage, aggregateAcceptanceReport, evaluateAcceptance, resolveEffectiveAcceptance } from "../shared/acceptance.ts";
|
|
70
69
|
import type { ChainOutputMap } from "../../shared/types.ts";
|
|
71
70
|
|
|
72
71
|
type RunSyncDependency = typeof runSync;
|
|
@@ -100,7 +99,7 @@ interface ChainExecutionDetailsInput {
|
|
|
100
99
|
outputs?: ChainOutputMap;
|
|
101
100
|
currentFlatIndex?: number;
|
|
102
101
|
dynamicChildren?: Record<number, Array<{ agent: string; label?: string; flatIndex: number; itemKey: string; outputName?: string; structured?: boolean; error?: string }>>;
|
|
103
|
-
dynamicGroupStatuses?: Record<number, { status: "pending" | "running" | "completed" | "failed" | "paused" | "detached"; error?: string
|
|
102
|
+
dynamicGroupStatuses?: Record<number, { status: "pending" | "running" | "completed" | "failed" | "paused" | "detached"; error?: string }>;
|
|
104
103
|
}
|
|
105
104
|
|
|
106
105
|
interface ParallelChainRunInput {
|
|
@@ -297,8 +296,6 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
297
296
|
preferredModelProvider: input.ctx.model?.provider,
|
|
298
297
|
skills: behavior.skills === false ? [] : behavior.skills,
|
|
299
298
|
structuredOutput: structuredRuntime,
|
|
300
|
-
acceptance: task.acceptance,
|
|
301
|
-
acceptanceContext: { mode: "chain" },
|
|
302
299
|
onUpdate: input.onUpdate
|
|
303
300
|
? (progressUpdate) => {
|
|
304
301
|
const stepResults = progressUpdate.details?.results || [];
|
|
@@ -785,30 +782,6 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
785
782
|
stepIndex,
|
|
786
783
|
};
|
|
787
784
|
dynamicGroupStatuses[stepIndex] = { status: "completed" };
|
|
788
|
-
if (step.acceptance !== undefined) {
|
|
789
|
-
const effectiveGroupAcceptance = resolveEffectiveAcceptance({
|
|
790
|
-
explicit: step.acceptance,
|
|
791
|
-
agentName: step.parallel.agent,
|
|
792
|
-
task: step.parallel.task ?? originalTask,
|
|
793
|
-
mode: "chain",
|
|
794
|
-
dynamicGroup: true,
|
|
795
|
-
});
|
|
796
|
-
const groupAcceptance = await evaluateAcceptance({
|
|
797
|
-
acceptance: effectiveGroupAcceptance,
|
|
798
|
-
output: "",
|
|
799
|
-
report: aggregateAcceptanceReport({
|
|
800
|
-
results: [],
|
|
801
|
-
notes: "Dynamic fanout produced 0 results.",
|
|
802
|
-
}),
|
|
803
|
-
cwd: cwd ?? ctx.cwd,
|
|
804
|
-
});
|
|
805
|
-
dynamicGroupStatuses[stepIndex].acceptance = groupAcceptance;
|
|
806
|
-
const groupAcceptanceFailure = acceptanceFailureMessage(groupAcceptance);
|
|
807
|
-
if (groupAcceptanceFailure) {
|
|
808
|
-
dynamicGroupStatuses[stepIndex] = { status: "failed", error: groupAcceptanceFailure, acceptance: groupAcceptance };
|
|
809
|
-
return buildChainExecutionErrorResult(groupAcceptanceFailure, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }));
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
785
|
prev = "Dynamic fanout produced 0 results.";
|
|
813
786
|
continue;
|
|
814
787
|
}
|
|
@@ -942,28 +915,6 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
942
915
|
stepIndex,
|
|
943
916
|
};
|
|
944
917
|
dynamicGroupStatuses[stepIndex] = { status: "completed" };
|
|
945
|
-
const effectiveGroupAcceptance = resolveEffectiveAcceptance({
|
|
946
|
-
explicit: step.acceptance,
|
|
947
|
-
agentName: step.parallel.agent,
|
|
948
|
-
task: step.parallel.task ?? originalTask,
|
|
949
|
-
mode: "chain",
|
|
950
|
-
dynamicGroup: true,
|
|
951
|
-
});
|
|
952
|
-
const groupAcceptance = await evaluateAcceptance({
|
|
953
|
-
acceptance: effectiveGroupAcceptance,
|
|
954
|
-
output: "",
|
|
955
|
-
report: aggregateAcceptanceReport({
|
|
956
|
-
results: parallelResults,
|
|
957
|
-
notes: `Dynamic fanout collected ${collected.length} result(s) into ${step.collect.as}.`,
|
|
958
|
-
}),
|
|
959
|
-
cwd: cwd ?? ctx.cwd,
|
|
960
|
-
});
|
|
961
|
-
dynamicGroupStatuses[stepIndex].acceptance = groupAcceptance;
|
|
962
|
-
const groupAcceptanceFailure = acceptanceFailureMessage(groupAcceptance);
|
|
963
|
-
if (groupAcceptanceFailure) {
|
|
964
|
-
dynamicGroupStatuses[stepIndex] = { status: "failed", error: groupAcceptanceFailure, acceptance: groupAcceptance };
|
|
965
|
-
return buildChainExecutionErrorResult(groupAcceptanceFailure, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex - dynamicParallelStep.parallel.length }));
|
|
966
|
-
}
|
|
967
918
|
const taskResults: ParallelTaskResult[] = parallelResults.map((result, i) => ({
|
|
968
919
|
agent: result.agent,
|
|
969
920
|
taskIndex: i,
|
|
@@ -1078,8 +1029,6 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
1078
1029
|
preferredModelProvider: ctx.model?.provider,
|
|
1079
1030
|
skills: behavior.skills === false ? [] : behavior.skills,
|
|
1080
1031
|
structuredOutput: structuredRuntime,
|
|
1081
|
-
acceptance: seqStep.acceptance,
|
|
1082
|
-
acceptanceContext: { mode: "chain" },
|
|
1083
1032
|
onUpdate: onUpdate
|
|
1084
1033
|
? (p) => {
|
|
1085
1034
|
const stepResults = p.details?.results || [];
|
|
@@ -43,12 +43,17 @@ import {
|
|
|
43
43
|
extractTextFromContent,
|
|
44
44
|
} from "../../shared/utils.ts";
|
|
45
45
|
import { buildSkillInjection, resolveSkillsWithFallback } from "../../agents/skills.ts";
|
|
46
|
-
import { evaluateCompletionMutationGuard } from "../shared/completion-guard.ts";
|
|
47
46
|
import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
|
|
48
47
|
import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
|
|
49
48
|
import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
|
|
50
49
|
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir } from "../shared/pi-args.ts";
|
|
51
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
STRUCTURED_OUTPUT_MAX_CORRECTIVE_PROMPTS,
|
|
52
|
+
formatStructuredOutputCorrectionPrompt,
|
|
53
|
+
isStructuredOutputContractError,
|
|
54
|
+
latestStructuredOutputToolErrorFromMessages,
|
|
55
|
+
readStructuredOutput,
|
|
56
|
+
} from "../shared/structured-output.ts";
|
|
52
57
|
import { captureSingleOutputSnapshot, formatSavedOutputReference, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
53
58
|
import {
|
|
54
59
|
buildModelCandidates,
|
|
@@ -78,10 +83,8 @@ import {
|
|
|
78
83
|
resolveSubagentModelFastMode,
|
|
79
84
|
} from "../../shared/fast-mode.ts";
|
|
80
85
|
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
81
|
-
import { acceptanceFailureMessage, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport } from "../shared/acceptance.ts";
|
|
82
86
|
|
|
83
87
|
const artifactOutputByResult = new WeakMap<SingleResult, string>();
|
|
84
|
-
const acceptanceOutputByResult = new WeakMap<SingleResult, string>();
|
|
85
88
|
const modelFailureSignalByResult = new WeakMap<SingleResult, unknown>();
|
|
86
89
|
|
|
87
90
|
function emptyUsage(): Usage {
|
|
@@ -105,17 +108,6 @@ function appendRecentOutput(progress: AgentProgress, lines: string[]): void {
|
|
|
105
108
|
}
|
|
106
109
|
}
|
|
107
110
|
|
|
108
|
-
function stripAcceptanceReportsFromMessages(messages: Message[] | undefined): void {
|
|
109
|
-
for (const message of messages ?? []) {
|
|
110
|
-
if (message.role !== "assistant" || !Array.isArray(message.content)) continue;
|
|
111
|
-
for (const part of message.content) {
|
|
112
|
-
if (part.type === "text" && "text" in part && typeof part.text === "string") {
|
|
113
|
-
part.text = stripAcceptanceReport(part.text);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
111
|
function snapshotProgress(progress: AgentProgress): AgentProgress {
|
|
120
112
|
return {
|
|
121
113
|
...progress,
|
|
@@ -157,38 +149,47 @@ function extractUpdateText(update: RunSyncUpdate): string | undefined {
|
|
|
157
149
|
return text || undefined;
|
|
158
150
|
}
|
|
159
151
|
|
|
160
|
-
|
|
152
|
+
function terminalUpdateFailureText(update: RunSyncUpdate): string | undefined {
|
|
161
153
|
const result = update.details?.results?.[0];
|
|
162
|
-
if (!result) return
|
|
154
|
+
if (!result) return undefined;
|
|
163
155
|
const progress = update.details?.progress?.[0];
|
|
164
156
|
const status = result.progress?.status ?? progress?.status;
|
|
165
|
-
if (status !== "failed") return
|
|
166
|
-
|
|
157
|
+
if (status !== "failed") return undefined;
|
|
158
|
+
return result.error
|
|
167
159
|
?? result.progress?.error
|
|
168
160
|
?? progress?.error
|
|
169
161
|
?? extractUpdateText(update);
|
|
170
|
-
return isRetryableModelFailure(failureText);
|
|
171
162
|
}
|
|
172
163
|
|
|
164
|
+
export function shouldSuppressIntermediateRetryableFailureUpdate(update: RunSyncUpdate): boolean {
|
|
165
|
+
return isRetryableModelFailure(terminalUpdateFailureText(update));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function shouldSuppressIntermediateStructuredOutputFailureUpdate(update: RunSyncUpdate): boolean {
|
|
169
|
+
return isStructuredOutputContractError(terminalUpdateFailureText(update));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
type RunSingleAttemptShared = {
|
|
173
|
+
sessionEnabled: boolean;
|
|
174
|
+
systemPrompt: string;
|
|
175
|
+
resolvedSkillNames?: string[];
|
|
176
|
+
skillsWarning?: string;
|
|
177
|
+
jsonlPath?: string;
|
|
178
|
+
artifactPaths?: ArtifactPaths;
|
|
179
|
+
attemptNotes: string[];
|
|
180
|
+
outputSnapshot?: SingleOutputSnapshot;
|
|
181
|
+
fastModeSettings: CodexFastModeResolvedSettings;
|
|
182
|
+
fastModeScope: CodexFastModeScope;
|
|
183
|
+
originalTask?: string;
|
|
184
|
+
};
|
|
185
|
+
|
|
173
186
|
async function runSingleAttempt(
|
|
174
187
|
runtimeCwd: string,
|
|
175
188
|
agent: AgentConfig,
|
|
176
189
|
task: string,
|
|
177
190
|
model: string | undefined,
|
|
178
191
|
options: RunSyncOptions,
|
|
179
|
-
shared:
|
|
180
|
-
sessionEnabled: boolean;
|
|
181
|
-
systemPrompt: string;
|
|
182
|
-
resolvedSkillNames?: string[];
|
|
183
|
-
skillsWarning?: string;
|
|
184
|
-
jsonlPath?: string;
|
|
185
|
-
artifactPaths?: ArtifactPaths;
|
|
186
|
-
attemptNotes: string[];
|
|
187
|
-
outputSnapshot?: SingleOutputSnapshot;
|
|
188
|
-
fastModeSettings: CodexFastModeResolvedSettings;
|
|
189
|
-
fastModeScope: CodexFastModeScope;
|
|
190
|
-
originalTask?: string;
|
|
191
|
-
},
|
|
192
|
+
shared: RunSingleAttemptShared,
|
|
192
193
|
): Promise<SingleResult> {
|
|
193
194
|
const modelArg = applyThinkingSuffix(model, agent.thinking);
|
|
194
195
|
const runCwd = options.cwd ?? runtimeCwd;
|
|
@@ -283,8 +284,6 @@ async function runSingleAttempt(
|
|
|
283
284
|
workflowStageSubagentGuard: options.workflowStageSubagentGuard,
|
|
284
285
|
}),
|
|
285
286
|
};
|
|
286
|
-
let observedMutationAttempt = false;
|
|
287
|
-
|
|
288
287
|
const exitCode = await new Promise<number>((resolve) => {
|
|
289
288
|
const spawnSpec = getPiSpawnCommand(args);
|
|
290
289
|
const proc = spawn(spawnSpec.command, spawnSpec.args, {
|
|
@@ -517,7 +516,6 @@ async function runSingleAttempt(
|
|
|
517
516
|
progress.currentToolStartedAt = now;
|
|
518
517
|
progress.currentPath = resolveCurrentPath(evt.toolName, toolArgs);
|
|
519
518
|
const mutates = isMutatingTool(evt.toolName, toolArgs);
|
|
520
|
-
observedMutationAttempt = observedMutationAttempt || mutates;
|
|
521
519
|
pendingToolResult = { tool: evt.toolName ?? "tool", path: progress.currentPath, mutates, startedAt: now };
|
|
522
520
|
fireUpdate();
|
|
523
521
|
}
|
|
@@ -774,36 +772,10 @@ async function runSingleAttempt(
|
|
|
774
772
|
durationMs: progress.durationMs,
|
|
775
773
|
};
|
|
776
774
|
|
|
777
|
-
|
|
778
|
-
let fullOutput = stripAcceptanceReport(acceptanceOutput);
|
|
779
|
-
const completionGuard = result.exitCode === 0 && !result.error && agent.completionGuard !== false
|
|
780
|
-
? evaluateCompletionMutationGuard({
|
|
781
|
-
agent: agent.name,
|
|
782
|
-
task: shared.originalTask ?? task,
|
|
783
|
-
messages: result.messages ?? [],
|
|
784
|
-
tools: agent.tools,
|
|
785
|
-
mcpDirectTools: agent.mcpDirectTools,
|
|
786
|
-
})
|
|
787
|
-
: undefined;
|
|
788
|
-
if (completionGuard?.triggered && !observedMutationAttempt) {
|
|
789
|
-
result.exitCode = 1;
|
|
790
|
-
result.error = "Subagent completed without making edits for an implementation task.\nIt appears to have returned planning or scratchpad output instead of applying changes.";
|
|
791
|
-
progress.status = "failed";
|
|
792
|
-
progress.error = result.error;
|
|
793
|
-
emitControlEvent(buildControlEvent({
|
|
794
|
-
from: progress.activityState,
|
|
795
|
-
to: "needs_attention",
|
|
796
|
-
runId: options.runId ?? agent.name,
|
|
797
|
-
agent: agent.name,
|
|
798
|
-
index: options.index,
|
|
799
|
-
ts: Date.now(),
|
|
800
|
-
message: `${agent.name} completed without making edits for an implementation task`,
|
|
801
|
-
reason: "completion_guard",
|
|
802
|
-
}));
|
|
803
|
-
}
|
|
775
|
+
let fullOutput = getFinalOutput(result.messages ?? []);
|
|
804
776
|
if (options.outputPath && result.exitCode === 0) {
|
|
805
777
|
const resolvedOutput = resolveSingleOutput(options.outputPath, fullOutput, shared.outputSnapshot);
|
|
806
|
-
fullOutput =
|
|
778
|
+
fullOutput = resolvedOutput.fullOutput;
|
|
807
779
|
result.savedOutputPath = resolvedOutput.savedPath;
|
|
808
780
|
result.outputSaveError = resolvedOutput.saveError;
|
|
809
781
|
if (resolvedOutput.savedPath) {
|
|
@@ -811,7 +783,6 @@ async function runSingleAttempt(
|
|
|
811
783
|
}
|
|
812
784
|
}
|
|
813
785
|
artifactOutputByResult.set(result, fullOutput);
|
|
814
|
-
acceptanceOutputByResult.set(result, acceptanceOutput);
|
|
815
786
|
result.outputMode = options.outputMode ?? "inline";
|
|
816
787
|
result.finalOutput = options.outputMode === "file-only" && result.savedOutputPath && result.outputReference
|
|
817
788
|
? result.outputReference.message
|
|
@@ -834,6 +805,71 @@ async function runSingleAttempt(
|
|
|
834
805
|
return result;
|
|
835
806
|
}
|
|
836
807
|
|
|
808
|
+
async function runSingleAttemptWithStructuredOutputRetries(
|
|
809
|
+
runtimeCwd: string,
|
|
810
|
+
agent: AgentConfig,
|
|
811
|
+
task: string,
|
|
812
|
+
model: string | undefined,
|
|
813
|
+
options: RunSyncOptions,
|
|
814
|
+
shared: RunSingleAttemptShared,
|
|
815
|
+
): Promise<SingleResult> {
|
|
816
|
+
let nextTask = task;
|
|
817
|
+
let correctiveAttempts = 0;
|
|
818
|
+
let finalResult: SingleResult | undefined;
|
|
819
|
+
const aggregateUsage = emptyUsage();
|
|
820
|
+
let totalToolCount = 0;
|
|
821
|
+
let totalDurationMs = 0;
|
|
822
|
+
|
|
823
|
+
while (true) {
|
|
824
|
+
const suppressIntermediateStructuredOutputFailure = options.structuredOutput !== undefined
|
|
825
|
+
&& correctiveAttempts < STRUCTURED_OUTPUT_MAX_CORRECTIVE_PROMPTS
|
|
826
|
+
&& options.onUpdate !== undefined;
|
|
827
|
+
const attemptOptions = suppressIntermediateStructuredOutputFailure
|
|
828
|
+
? {
|
|
829
|
+
...options,
|
|
830
|
+
onUpdate: (update: RunSyncUpdate) => {
|
|
831
|
+
if (shouldSuppressIntermediateStructuredOutputFailureUpdate(update)) return;
|
|
832
|
+
options.onUpdate?.(update);
|
|
833
|
+
},
|
|
834
|
+
}
|
|
835
|
+
: options;
|
|
836
|
+
const result = await runSingleAttempt(runtimeCwd, agent, nextTask, model, attemptOptions, shared);
|
|
837
|
+
finalResult = result;
|
|
838
|
+
sumUsage(aggregateUsage, result.usage);
|
|
839
|
+
totalToolCount += result.progressSummary?.toolCount ?? 0;
|
|
840
|
+
totalDurationMs += result.progressSummary?.durationMs ?? 0;
|
|
841
|
+
|
|
842
|
+
if (!options.structuredOutput || !isStructuredOutputContractError(result.error)) break;
|
|
843
|
+
const correctionError = latestStructuredOutputToolErrorFromMessages(result.messages) ?? result.error ?? "Structured output contract failed.";
|
|
844
|
+
if (correctiveAttempts >= STRUCTURED_OUTPUT_MAX_CORRECTIVE_PROMPTS) {
|
|
845
|
+
result.error = correctionError;
|
|
846
|
+
break;
|
|
847
|
+
}
|
|
848
|
+
correctiveAttempts += 1;
|
|
849
|
+
nextTask = formatStructuredOutputCorrectionPrompt({
|
|
850
|
+
originalTask: task,
|
|
851
|
+
error: correctionError,
|
|
852
|
+
attempt: correctiveAttempts,
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
const result = finalResult ?? {
|
|
857
|
+
agent: agent.name,
|
|
858
|
+
task,
|
|
859
|
+
exitCode: 1,
|
|
860
|
+
messages: [],
|
|
861
|
+
usage: emptyUsage(),
|
|
862
|
+
error: "Subagent did not produce a result.",
|
|
863
|
+
} satisfies SingleResult;
|
|
864
|
+
result.usage = aggregateUsage;
|
|
865
|
+
result.progressSummary = {
|
|
866
|
+
toolCount: totalToolCount,
|
|
867
|
+
tokens: aggregateUsage.input + aggregateUsage.output,
|
|
868
|
+
durationMs: totalDurationMs,
|
|
869
|
+
};
|
|
870
|
+
return result;
|
|
871
|
+
}
|
|
872
|
+
|
|
837
873
|
/**
|
|
838
874
|
* Run a subagent synchronously (blocking until complete)
|
|
839
875
|
*/
|
|
@@ -869,17 +905,6 @@ export async function runSync(
|
|
|
869
905
|
}
|
|
870
906
|
|
|
871
907
|
const shareEnabled = options.share === true;
|
|
872
|
-
const effectiveAcceptance = resolveEffectiveAcceptance({
|
|
873
|
-
explicit: options.acceptance,
|
|
874
|
-
agentName,
|
|
875
|
-
task,
|
|
876
|
-
mode: options.acceptanceContext?.mode ?? "single",
|
|
877
|
-
async: options.acceptanceContext?.async,
|
|
878
|
-
dynamic: options.acceptanceContext?.dynamic,
|
|
879
|
-
dynamicGroup: options.acceptanceContext?.dynamicGroup,
|
|
880
|
-
});
|
|
881
|
-
const acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance);
|
|
882
|
-
const taskWithAcceptance = acceptancePrompt ? `${task}\n${acceptancePrompt}` : task;
|
|
883
908
|
const sessionEnabled = Boolean(options.sessionFile || options.sessionDir) || shareEnabled;
|
|
884
909
|
const skillNames = options.skills ?? agent.skills ?? [];
|
|
885
910
|
const skillCwd = options.cwd ?? runtimeCwd;
|
|
@@ -925,7 +950,7 @@ export async function runSync(
|
|
|
925
950
|
artifactPathsResult = getArtifactPaths(options.artifactsDir, options.runId, agentName, options.index);
|
|
926
951
|
ensureArtifactsDir(options.artifactsDir);
|
|
927
952
|
if (options.artifactConfig?.includeInput !== false) {
|
|
928
|
-
|
|
953
|
+
writeArtifact(artifactPathsResult.inputPath, `# Task for ${agentName}\n\n${task}`);
|
|
929
954
|
}
|
|
930
955
|
if (options.artifactConfig?.includeJsonl !== false) {
|
|
931
956
|
jsonlPath = artifactPathsResult.jsonlPath;
|
|
@@ -949,7 +974,7 @@ export async function runSync(
|
|
|
949
974
|
},
|
|
950
975
|
};
|
|
951
976
|
}
|
|
952
|
-
const result = await
|
|
977
|
+
const result = await runSingleAttemptWithStructuredOutputRetries(runtimeCwd, agent, task, candidate, attemptOptions, {
|
|
953
978
|
sessionEnabled,
|
|
954
979
|
systemPrompt,
|
|
955
980
|
resolvedSkillNames: resolvedSkills.length > 0 ? resolvedSkills.map((skill) => skill.name) : undefined,
|
|
@@ -1056,21 +1081,5 @@ export async function runSync(
|
|
|
1056
1081
|
if (sessionFile) result.sessionFile = sessionFile;
|
|
1057
1082
|
}
|
|
1058
1083
|
|
|
1059
|
-
result.acceptance = await evaluateAcceptance({
|
|
1060
|
-
acceptance: effectiveAcceptance,
|
|
1061
|
-
output: acceptanceOutputByResult.get(result) ?? result.finalOutput ?? "",
|
|
1062
|
-
cwd: options.cwd ?? runtimeCwd,
|
|
1063
|
-
});
|
|
1064
|
-
const acceptanceFailure = acceptanceFailureMessage(result.acceptance);
|
|
1065
|
-
stripAcceptanceReportsFromMessages(result.messages);
|
|
1066
|
-
if (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.detached && !result.interrupted) {
|
|
1067
|
-
result.exitCode = 1;
|
|
1068
|
-
result.error = result.error ? `${result.error}\n${acceptanceFailure}` : acceptanceFailure;
|
|
1069
|
-
if (result.progress) {
|
|
1070
|
-
result.progress.status = "failed";
|
|
1071
|
-
result.progress.error = result.error;
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
1084
|
return result;
|
|
1076
1085
|
}
|
|
@@ -64,7 +64,6 @@ import {
|
|
|
64
64
|
} from "../shared/worktree.ts";
|
|
65
65
|
import {
|
|
66
66
|
type AgentProgress,
|
|
67
|
-
type AcceptanceInput,
|
|
68
67
|
type ArtifactConfig,
|
|
69
68
|
type ArtifactPaths,
|
|
70
69
|
type ControlConfig,
|
|
@@ -109,7 +108,6 @@ interface TaskParam {
|
|
|
109
108
|
progress?: boolean;
|
|
110
109
|
model?: string;
|
|
111
110
|
skill?: string | string[] | boolean;
|
|
112
|
-
acceptance?: AcceptanceInput;
|
|
113
111
|
}
|
|
114
112
|
|
|
115
113
|
export interface SubagentParamsLike {
|
|
@@ -143,7 +141,6 @@ export interface SubagentParamsLike {
|
|
|
143
141
|
outputMode?: "inline" | "file-only";
|
|
144
142
|
agentScope?: string;
|
|
145
143
|
chainDir?: string;
|
|
146
|
-
acceptance?: AcceptanceInput;
|
|
147
144
|
}
|
|
148
145
|
|
|
149
146
|
export interface SubagentExecutorRuntimeDeps {
|
|
@@ -1150,7 +1147,6 @@ function runAsyncPath(data: ExecutionContextData, deps: ResolvedExecutorDeps): S
|
|
|
1150
1147
|
...(task.outputMode !== undefined ? { outputMode: task.outputMode } : {}),
|
|
1151
1148
|
...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
|
|
1152
1149
|
...(task.progress !== undefined ? { progress: task.progress } : {}),
|
|
1153
|
-
...(task.acceptance !== undefined ? { acceptance: task.acceptance } : {}),
|
|
1154
1150
|
}));
|
|
1155
1151
|
return deps.runtime.executeAsyncChain(id, {
|
|
1156
1152
|
chain: [{
|
|
@@ -1252,7 +1248,6 @@ function runAsyncPath(data: ExecutionContextData, deps: ResolvedExecutorDeps): S
|
|
|
1252
1248
|
controlIntercomTarget,
|
|
1253
1249
|
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(agent, index) : undefined,
|
|
1254
1250
|
nestedRoute,
|
|
1255
|
-
acceptance: params.acceptance,
|
|
1256
1251
|
});
|
|
1257
1252
|
}
|
|
1258
1253
|
|
|
@@ -1577,8 +1572,6 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
1577
1572
|
preferredModelProvider: input.ctx.model?.provider,
|
|
1578
1573
|
currentModel: currentModelFullId(input.ctx.model),
|
|
1579
1574
|
skills: effectiveSkills === false ? [] : effectiveSkills,
|
|
1580
|
-
acceptance: task.acceptance,
|
|
1581
|
-
acceptanceContext: { mode: "parallel" },
|
|
1582
1575
|
onUpdate: input.onUpdate
|
|
1583
1576
|
? (progressUpdate) => {
|
|
1584
1577
|
const stepResults = progressUpdate.details?.results || [];
|
|
@@ -1771,7 +1764,6 @@ async function runParallelPath(data: ExecutionContextData, deps: ResolvedExecuto
|
|
|
1771
1764
|
...(behaviorOverrides[i]?.outputMode !== undefined ? { outputMode: behaviorOverrides[i]!.outputMode } : {}),
|
|
1772
1765
|
...(behaviorOverrides[i]?.reads !== undefined ? { reads: behaviorOverrides[i]!.reads } : {}),
|
|
1773
1766
|
...(progress !== undefined ? { progress } : {}),
|
|
1774
|
-
...(t.acceptance !== undefined ? { acceptance: t.acceptance } : {}),
|
|
1775
1767
|
};
|
|
1776
1768
|
});
|
|
1777
1769
|
return deps.runtime.executeAsyncChain(id, {
|
|
@@ -2154,8 +2146,6 @@ async function runSinglePath(data: ExecutionContextData, deps: ResolvedExecutorD
|
|
|
2154
2146
|
preferredModelProvider: currentProvider,
|
|
2155
2147
|
currentModel: currentModelFullId(ctx.model),
|
|
2156
2148
|
skills: effectiveSkills,
|
|
2157
|
-
acceptance: params.acceptance,
|
|
2158
|
-
acceptanceContext: { mode: "single" },
|
|
2159
2149
|
});
|
|
2160
2150
|
if (foregroundControl?.currentIndex === 0) {
|
|
2161
2151
|
foregroundControl.interrupt = undefined;
|
|
@@ -40,16 +40,17 @@ const SAFE_OUTPUT_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
|
40
40
|
const ITEM_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
41
41
|
const ITEM_REF_PATTERN = /\{([A-Za-z_][A-Za-z0-9_]*)(?:\.([^{}]+))?\}/g;
|
|
42
42
|
const RESERVED_TEMPLATE_NAMES = new Set(["task", "previous", "chain_dir", "outputs"]);
|
|
43
|
-
const DYNAMIC_STEP_KEYS = new Set(["expand", "parallel", "collect", "concurrency", "failFast", "phase", "label"
|
|
44
|
-
const RUNNER_DYNAMIC_STEP_KEYS = new Set(
|
|
43
|
+
const DYNAMIC_STEP_KEYS = new Set(["expand", "parallel", "collect", "concurrency", "failFast", "phase", "label"]);
|
|
44
|
+
const RUNNER_DYNAMIC_STEP_KEYS = new Set(DYNAMIC_STEP_KEYS);
|
|
45
|
+
const REMOVED_LEGACY_DYNAMIC_KEYS = new Set(["acceptance"]);
|
|
45
46
|
const DYNAMIC_EXPAND_KEYS = new Set(["from", "item", "key", "maxItems", "onEmpty"]);
|
|
46
47
|
const DYNAMIC_EXPAND_FROM_KEYS = new Set(["output", "path"]);
|
|
47
|
-
const DYNAMIC_PARALLEL_KEYS = new Set(["agent", "task", "phase", "label", "outputSchema", "cwd", "output", "outputMode", "reads", "progress", "skill", "model"
|
|
48
|
+
const DYNAMIC_PARALLEL_KEYS = new Set(["agent", "task", "phase", "label", "outputSchema", "cwd", "output", "outputMode", "reads", "progress", "skill", "model"]);
|
|
48
49
|
const RUNNER_DYNAMIC_PARALLEL_KEYS = new Set([
|
|
49
50
|
...DYNAMIC_PARALLEL_KEYS,
|
|
50
51
|
"outputName", "structured", "inheritProjectContext", "inheritSkills", "skills", "outputPath", "maxSubagentDepth",
|
|
51
|
-
"structuredOutput", "structuredOutputSchema", "tools", "extensions", "mcpDirectTools", "
|
|
52
|
-
"systemPromptMode", "thinking", "modelCandidates", "sessionFile",
|
|
52
|
+
"structuredOutput", "structuredOutputSchema", "tools", "extensions", "mcpDirectTools", "systemPrompt",
|
|
53
|
+
"systemPromptMode", "thinking", "modelCandidates", "sessionFile",
|
|
53
54
|
]);
|
|
54
55
|
const DYNAMIC_COLLECT_KEYS = new Set(["as", "outputSchema"]);
|
|
55
56
|
|
|
@@ -146,10 +147,16 @@ export function resolveItemTemplate(template: string, itemName: string, item: un
|
|
|
146
147
|
function assertOnlyKeys(value: unknown, allowed: Set<string>, label: string): void {
|
|
147
148
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new DynamicFanoutError(`${label} must be an object.`);
|
|
148
149
|
for (const key of Object.keys(value)) {
|
|
150
|
+
if (REMOVED_LEGACY_DYNAMIC_KEYS.has(key)) continue;
|
|
149
151
|
if (!allowed.has(key)) throw new DynamicFanoutError(`${label} does not support field '${key}'.`);
|
|
150
152
|
}
|
|
151
153
|
}
|
|
152
154
|
|
|
155
|
+
function stripRemovedLegacyDynamicKeys<T extends object>(value: T): T {
|
|
156
|
+
const entries = Object.entries(value).filter(([key]) => !REMOVED_LEGACY_DYNAMIC_KEYS.has(key));
|
|
157
|
+
return entries.length === Object.keys(value).length ? value : Object.fromEntries(entries) as T;
|
|
158
|
+
}
|
|
159
|
+
|
|
153
160
|
export function assertNoUnresolvedItemReferences(template: string, itemName: string, label: string): void {
|
|
154
161
|
for (const match of template.matchAll(/\{([^{}]*)\}/g)) {
|
|
155
162
|
const raw = match[0]!;
|
|
@@ -247,11 +254,12 @@ export function materializeDynamicParallelStep(step: DynamicParallelStep, output
|
|
|
247
254
|
return { items, parallel: [], collectedOnEmpty: [] };
|
|
248
255
|
}
|
|
249
256
|
const itemName = step.expand.item ?? "item";
|
|
257
|
+
const parallelTemplate = stripRemovedLegacyDynamicKeys(step.parallel);
|
|
250
258
|
const parallel = items.map((entry) => {
|
|
251
|
-
const task = resolveItemTemplate(
|
|
252
|
-
const label =
|
|
259
|
+
const task = resolveItemTemplate(parallelTemplate.task ?? "{previous}", itemName, entry.item);
|
|
260
|
+
const label = parallelTemplate.label ? resolveItemTemplate(parallelTemplate.label, itemName, entry.item) : undefined;
|
|
253
261
|
return {
|
|
254
|
-
...
|
|
262
|
+
...parallelTemplate,
|
|
255
263
|
task,
|
|
256
264
|
...(label !== undefined ? { label } : {}),
|
|
257
265
|
};
|
|
@@ -18,7 +18,6 @@ export interface RunnerSubagentStep {
|
|
|
18
18
|
tools?: string[];
|
|
19
19
|
extensions?: string[];
|
|
20
20
|
mcpDirectTools?: string[];
|
|
21
|
-
completionGuard?: boolean;
|
|
22
21
|
systemPrompt?: string | null;
|
|
23
22
|
systemPromptMode?: "append" | "replace";
|
|
24
23
|
inheritProjectContext: boolean;
|
|
@@ -35,7 +34,6 @@ export interface RunnerSubagentStep {
|
|
|
35
34
|
outputPath: string;
|
|
36
35
|
};
|
|
37
36
|
structuredOutputSchema?: import("../../shared/types.ts").JsonSchemaObject;
|
|
38
|
-
effectiveAcceptance?: import("../../shared/types.ts").ResolvedAcceptanceConfig;
|
|
39
37
|
}
|
|
40
38
|
|
|
41
39
|
export interface ParallelStepGroup {
|
|
@@ -53,7 +51,6 @@ export interface DynamicRunnerGroup {
|
|
|
53
51
|
failFast?: boolean;
|
|
54
52
|
phase?: string;
|
|
55
53
|
label?: string;
|
|
56
|
-
effectiveAcceptance?: import("../../shared/types.ts").ResolvedAcceptanceConfig;
|
|
57
54
|
}
|
|
58
55
|
|
|
59
56
|
export type RunnerStep = RunnerSubagentStep | ParallelStepGroup | DynamicRunnerGroup;
|
|
@@ -3,11 +3,15 @@ import * as os from "node:os";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { APP_NAME } from "@bastani/atomic";
|
|
5
5
|
import { Compile } from "typebox/compile";
|
|
6
|
+
import type { Message } from "@earendil-works/pi-ai";
|
|
6
7
|
import type { JsonSchemaObject } from "../../shared/types.ts";
|
|
7
8
|
|
|
8
9
|
const ENV_PREFIX = APP_NAME.toUpperCase();
|
|
9
10
|
export const STRUCTURED_OUTPUT_SCHEMA_ENV = `${ENV_PREFIX}_SUBAGENT_STRUCTURED_OUTPUT_SCHEMA`;
|
|
10
11
|
export const STRUCTURED_OUTPUT_CAPTURE_ENV = `${ENV_PREFIX}_SUBAGENT_STRUCTURED_OUTPUT_CAPTURE`;
|
|
12
|
+
export const STRUCTURED_OUTPUT_TOOL_NAME = "structured_output";
|
|
13
|
+
export const STRUCTURED_OUTPUT_MAX_CORRECTIVE_PROMPTS = 3;
|
|
14
|
+
export const STRUCTURED_OUTPUT_MISSING_ERROR = "Missing structured_output call; this step has outputSchema and must finish by calling structured_output.";
|
|
11
15
|
|
|
12
16
|
export interface StructuredOutputRuntime {
|
|
13
17
|
schema: JsonSchemaObject;
|
|
@@ -50,12 +54,73 @@ export function validateStructuredOutputValue(schema: JsonSchemaObject, value: u
|
|
|
50
54
|
}
|
|
51
55
|
}
|
|
52
56
|
|
|
57
|
+
function textFromContent(content: unknown): string | undefined {
|
|
58
|
+
if (!Array.isArray(content)) return undefined;
|
|
59
|
+
const text = content
|
|
60
|
+
.map((block) => {
|
|
61
|
+
if (!block || typeof block !== "object") return "";
|
|
62
|
+
const record = block as { readonly type?: unknown; readonly text?: unknown };
|
|
63
|
+
return record.type === "text" && typeof record.text === "string" ? record.text : "";
|
|
64
|
+
})
|
|
65
|
+
.join("\n")
|
|
66
|
+
.trim();
|
|
67
|
+
return text.length > 0 ? text : undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function latestStructuredOutputToolErrorFromMessages(messages: readonly Message[] | undefined): string | undefined {
|
|
71
|
+
if (!messages) return undefined;
|
|
72
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
73
|
+
const message = messages[index];
|
|
74
|
+
if (message?.role !== "toolResult") continue;
|
|
75
|
+
if (message.toolName !== STRUCTURED_OUTPUT_TOOL_NAME) continue;
|
|
76
|
+
if (message.isError !== true) continue;
|
|
77
|
+
return textFromContent(message.content) ?? "structured_output tool call failed schema validation.";
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function isStructuredOutputContractError(error: string | undefined): boolean {
|
|
83
|
+
if (error === undefined) return false;
|
|
84
|
+
return error === STRUCTURED_OUTPUT_MISSING_ERROR
|
|
85
|
+
|| error.startsWith("Structured output validation failed:")
|
|
86
|
+
|| error.startsWith("Failed to read structured output:");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function formatStructuredOutputCorrectionPrompt(args: {
|
|
90
|
+
readonly originalTask: string;
|
|
91
|
+
readonly error: string;
|
|
92
|
+
readonly attempt: number;
|
|
93
|
+
readonly maxAttempts?: number;
|
|
94
|
+
}): string {
|
|
95
|
+
const maxAttempts = args.maxAttempts ?? STRUCTURED_OUTPUT_MAX_CORRECTIVE_PROMPTS;
|
|
96
|
+
return [
|
|
97
|
+
"The previous response failed this subagent's structured-output contract.",
|
|
98
|
+
"",
|
|
99
|
+
`Corrective attempt ${args.attempt}/${maxAttempts}.`,
|
|
100
|
+
"",
|
|
101
|
+
"Error:",
|
|
102
|
+
args.error,
|
|
103
|
+
"",
|
|
104
|
+
"You must finish by calling the `structured_output` tool exactly once with arguments matching the registered schema.",
|
|
105
|
+
"Do not answer with plain JSON text, Markdown, or prose. If you attempted `structured_output` and validation failed, correct the tool arguments and call `structured_output` again.",
|
|
106
|
+
"If the requested work is already complete, do not redo side effects unnecessarily; just report the completed result through `structured_output`.",
|
|
107
|
+
"",
|
|
108
|
+
"Original task:",
|
|
109
|
+
args.originalTask,
|
|
110
|
+
].join("\n");
|
|
111
|
+
}
|
|
112
|
+
|
|
53
113
|
export function readStructuredOutput(runtime: StructuredOutputRuntime): { value?: unknown; error?: string } {
|
|
54
114
|
if (!fs.existsSync(runtime.outputPath)) {
|
|
55
|
-
return { error:
|
|
115
|
+
return { error: STRUCTURED_OUTPUT_MISSING_ERROR };
|
|
56
116
|
}
|
|
57
117
|
try {
|
|
58
|
-
|
|
118
|
+
const value = JSON.parse(fs.readFileSync(runtime.outputPath, "utf-8")) as unknown;
|
|
119
|
+
const validation = validateStructuredOutputValue(runtime.schema, value);
|
|
120
|
+
if (validation.status === "invalid") {
|
|
121
|
+
return { error: `Structured output validation failed: ${validation.message}` };
|
|
122
|
+
}
|
|
123
|
+
return { value };
|
|
59
124
|
} catch (error) {
|
|
60
125
|
return { error: `Failed to read structured output: ${error instanceof Error ? error.message : String(error)}` };
|
|
61
126
|
}
|