imprint-mcp 0.7.0 → 0.7.2
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/README.md +66 -2
- package/package.json +1 -1
- package/prompts/auth-compile-agent.md +173 -22
- package/prompts/compile-agent.md +45 -8
- package/prompts/component-recovery.md +40 -0
- package/prompts/master-teach-decision.md +28 -3
- package/prompts/master-teach-focused-planner.md +18 -5
- package/src/cli.ts +26 -18
- package/src/imprint/agent-tool.ts +15 -0
- package/src/imprint/agent.ts +2 -11
- package/src/imprint/audit.ts +35 -13
- package/src/imprint/auth-browser-discovery.ts +276 -0
- package/src/imprint/auth-compile-agent.ts +513 -224
- package/src/imprint/auth-compile-tools.ts +213 -25
- package/src/imprint/auth-discovery-evidence.ts +81 -0
- package/src/imprint/auth-plan.ts +28 -0
- package/src/imprint/auth-verifier.ts +234 -20
- package/src/imprint/authentication-interaction.ts +137 -0
- package/src/imprint/backend-ladder.ts +271 -43
- package/src/imprint/build-plan.ts +2 -22
- package/src/imprint/cdp-browser-fetch.ts +242 -60
- package/src/imprint/chromium.ts +93 -29
- package/src/imprint/claude-cli-compile.ts +2 -0
- package/src/imprint/codex-cli-compile.ts +99 -46
- package/src/imprint/compile-agent-types.ts +9 -5
- package/src/imprint/compile-agent.ts +33 -0
- package/src/imprint/compile-tool-executor.ts +783 -0
- package/src/imprint/compile-tools.ts +42 -9
- package/src/imprint/compile-verification.ts +2 -0
- package/src/imprint/compile.ts +3 -0
- package/src/imprint/compiler-log.ts +16 -0
- package/src/imprint/component-recovery.ts +120 -0
- package/src/imprint/cookie-jar.ts +117 -7
- package/src/imprint/credential-extract.ts +9 -2
- package/src/imprint/credential-lock.ts +147 -0
- package/src/imprint/credential-observation.ts +261 -0
- package/src/imprint/credential-store.ts +284 -44
- package/src/imprint/live-component-recovery.ts +73 -0
- package/src/imprint/llm.ts +4 -3
- package/src/imprint/log.ts +16 -1
- package/src/imprint/login.ts +9 -0
- package/src/imprint/master-teach-agent-contracts.ts +40 -3
- package/src/imprint/master-teach-agents.ts +229 -14
- package/src/imprint/master-teach-checks.ts +8 -1
- package/src/imprint/master-teach-controller.ts +681 -138
- package/src/imprint/master-teach-prompt-projections.ts +2 -0
- package/src/imprint/mcp-cdp-sessions.ts +7 -2
- package/src/imprint/mcp-compile-server.ts +62 -850
- package/src/imprint/mcp-server.ts +667 -91
- package/src/imprint/mcp-session-recovery.ts +167 -0
- package/src/imprint/native-teach-agents.ts +232 -108
- package/src/imprint/runtime.ts +416 -47
- package/src/imprint/stealth-chromium.ts +67 -7
- package/src/imprint/teach-browser-resources.ts +88 -0
- package/src/imprint/teach-stage-progress.ts +38 -0
- package/src/imprint/terminal-user-prompt.ts +42 -0
- package/src/imprint/types.ts +51 -18
- package/src/imprint/user-interaction.ts +836 -0
|
@@ -84,6 +84,7 @@ import {
|
|
|
84
84
|
apiResearchInputsSha256,
|
|
85
85
|
apiResearchRequiredLinks,
|
|
86
86
|
apiResearchStableInputsSha256,
|
|
87
|
+
focusedPlannerResearchContext,
|
|
87
88
|
mechanicalProofFailures,
|
|
88
89
|
requestApiResearchStep,
|
|
89
90
|
requestBaselineMvpReview,
|
|
@@ -107,7 +108,6 @@ import {
|
|
|
107
108
|
EditableTeachingPlanSchema,
|
|
108
109
|
type EditableTeachingTool,
|
|
109
110
|
type ImplementationPlanPayload,
|
|
110
|
-
ImplementationPlanPayloadSchema,
|
|
111
111
|
bindImplementationPlanRef,
|
|
112
112
|
canonicalTeachingPlanJson,
|
|
113
113
|
chainInvocationForEdge,
|
|
@@ -163,9 +163,9 @@ import {
|
|
|
163
163
|
recordingRequestPrecedingEvent,
|
|
164
164
|
selectedRecordingEvidenceDocuments,
|
|
165
165
|
} from './replay-evidence.ts';
|
|
166
|
-
import { loadCredentialStore } from './runtime.ts';
|
|
167
166
|
import { resolveExplicitTeachingRecordings, resolveTeachingRecording } from './session-merge.ts';
|
|
168
167
|
import { TeachResearchMemory, readRunResearchJsonObject } from './teach-research-memory.ts';
|
|
168
|
+
import { withTeachStageProgress } from './teach-stage-progress.ts';
|
|
169
169
|
import { buildToolCandidatePayload, detectToolCandidates } from './tool-candidates.ts';
|
|
170
170
|
import type { SharedCompileContext, ToolCandidate } from './tool-candidates.ts';
|
|
171
171
|
import {
|
|
@@ -176,7 +176,22 @@ import {
|
|
|
176
176
|
type Workflow,
|
|
177
177
|
} from './types.ts';
|
|
178
178
|
|
|
179
|
+
import { compileAuthAgent } from './auth-compile-agent.ts';
|
|
180
|
+
import { attachRecordedAuthentication } from './authentication-interaction.ts';
|
|
181
|
+
import {
|
|
182
|
+
type ComponentRecoveryOwner,
|
|
183
|
+
ComponentRecoveryQueue,
|
|
184
|
+
runWithComponentRecovery,
|
|
185
|
+
} from './component-recovery.ts';
|
|
186
|
+
import { selectLiveComponentRecovery } from './live-component-recovery.ts';
|
|
179
187
|
import { NativeTeachAgents, currentNativeTeachAgents } from './native-teach-agents.ts';
|
|
188
|
+
import { withTeachBrowserResources } from './teach-browser-resources.ts';
|
|
189
|
+
import {
|
|
190
|
+
type OnUserPrompt,
|
|
191
|
+
UserInteraction,
|
|
192
|
+
currentUserInteraction,
|
|
193
|
+
withUserInteraction,
|
|
194
|
+
} from './user-interaction.ts';
|
|
180
195
|
|
|
181
196
|
const FOCUSED_COMPILE_CONCURRENCY = 4;
|
|
182
197
|
const SPECULATIVE_PLANNING_MIN_REMAINING_MS = 180_000;
|
|
@@ -208,6 +223,10 @@ export function revisionSeedArtifactNames(
|
|
|
208
223
|
}
|
|
209
224
|
|
|
210
225
|
export interface FreshTeachOptions {
|
|
226
|
+
/** Explicit run-owned adapters supplied by component builders/hosts. */
|
|
227
|
+
componentRecoveryOwners?: readonly ComponentRecoveryOwner[];
|
|
228
|
+
/** Present a question in the caller's UI. Missing input is never approval. */
|
|
229
|
+
onPrompt?: OnUserPrompt;
|
|
211
230
|
site?: string;
|
|
212
231
|
url?: string;
|
|
213
232
|
persistProfile?: boolean;
|
|
@@ -261,6 +280,7 @@ interface BuildWaveFailure {
|
|
|
261
280
|
compilerSummary?: string;
|
|
262
281
|
/** Bounded observations from the live backend that produced the failure. */
|
|
263
282
|
liveResponseObservations?: BackendResponseObservation[];
|
|
283
|
+
componentRecoveries?: number;
|
|
264
284
|
}
|
|
265
285
|
|
|
266
286
|
interface BuildWaveResult<Value> {
|
|
@@ -567,6 +587,7 @@ export function consumerResearchForCompiler(
|
|
|
567
587
|
|
|
568
588
|
/** Test seams are deliberately role-sized; production defaults use the shipped modules. */
|
|
569
589
|
interface FreshTeachControllerDependencies {
|
|
590
|
+
compileAuthTool: typeof compileAuthAgent;
|
|
570
591
|
now: () => Date;
|
|
571
592
|
runId: () => string;
|
|
572
593
|
record: typeof record;
|
|
@@ -595,6 +616,7 @@ interface FreshTeachControllerDependencies {
|
|
|
595
616
|
priorToolDir?: string;
|
|
596
617
|
apiResearchDir?: string;
|
|
597
618
|
apiResearchSummary?: string;
|
|
619
|
+
apiResearchAdvisory?: ApiResearchPlanAdvisory;
|
|
598
620
|
consumerResearch?: ConsumerResearchContext[];
|
|
599
621
|
revisionGuidance?: string;
|
|
600
622
|
revisionContext?: FocusedPlannerRevisionContext;
|
|
@@ -608,6 +630,7 @@ interface FreshTeachControllerDependencies {
|
|
|
608
630
|
}) => Promise<CompiledFocusedTool>;
|
|
609
631
|
runApiResearchTool: ApiToolRunner;
|
|
610
632
|
runApiTool: ApiToolRunner;
|
|
633
|
+
selectLiveComponentRecovery: typeof selectLiveComponentRecovery;
|
|
611
634
|
runPlaybookTool: (input: {
|
|
612
635
|
playbookPath: string;
|
|
613
636
|
site: string;
|
|
@@ -625,8 +648,12 @@ interface FreshTeachControllerDependencies {
|
|
|
625
648
|
}) => Promise<void>;
|
|
626
649
|
}
|
|
627
650
|
|
|
628
|
-
const
|
|
629
|
-
|
|
651
|
+
const runApiToolWithLadder: ApiToolRunner = async (input) =>
|
|
652
|
+
await withTeachBrowserResources(input.cdpPool, input.signal, (cdpPool) =>
|
|
653
|
+
executeApiToolWithLadder({ ...input, cdpPool }),
|
|
654
|
+
);
|
|
655
|
+
|
|
656
|
+
const executeApiToolWithLadder: ApiToolRunner = async ({
|
|
630
657
|
cdpPool,
|
|
631
658
|
workflowPath,
|
|
632
659
|
parameters,
|
|
@@ -636,25 +663,6 @@ const runApiToolWithLadder: ApiToolRunner = async ({
|
|
|
636
663
|
onRawResponses,
|
|
637
664
|
onResponseEvidence,
|
|
638
665
|
}) => {
|
|
639
|
-
if (cdpPool) {
|
|
640
|
-
const tool = resolveWorkflowTool(workflowPath);
|
|
641
|
-
const context = teachingPlanContentSha256({
|
|
642
|
-
credentials: await loadCredentialStore(tool.site),
|
|
643
|
-
bootstrap: tool.workflow.bootstrap,
|
|
644
|
-
site: tool.site,
|
|
645
|
-
tool: tool.workflow.toolName,
|
|
646
|
-
});
|
|
647
|
-
const contexts = researchPoolContexts.get(cdpPool) ?? new Map<string, string>();
|
|
648
|
-
const prefix = `${tool.site}\u0000${tool.workflow.toolName}\u0000`;
|
|
649
|
-
const previous = contexts.get(prefix);
|
|
650
|
-
if (previous && previous !== context) {
|
|
651
|
-
const browsers = [...cdpPool.entries()].filter(([key]) => key.startsWith(prefix));
|
|
652
|
-
for (const [key] of browsers) cdpPool.delete(key);
|
|
653
|
-
await Promise.allSettled(browsers.map(([, browser]) => browser.close()));
|
|
654
|
-
}
|
|
655
|
-
contexts.set(prefix, context);
|
|
656
|
-
researchPoolContexts.set(cdpPool, contexts);
|
|
657
|
-
}
|
|
658
666
|
const run = await runWorkflowWithLadder({
|
|
659
667
|
cdpPool,
|
|
660
668
|
workflowPath,
|
|
@@ -797,6 +805,7 @@ export async function verifyRecordingMvp(input: {
|
|
|
797
805
|
}
|
|
798
806
|
|
|
799
807
|
const defaultDependencies: FreshTeachControllerDependencies = {
|
|
808
|
+
compileAuthTool: compileAuthAgent,
|
|
800
809
|
now: () => new Date(),
|
|
801
810
|
runId: () => randomUUID(),
|
|
802
811
|
record,
|
|
@@ -810,6 +819,7 @@ const defaultDependencies: FreshTeachControllerDependencies = {
|
|
|
810
819
|
requestMasterDecision,
|
|
811
820
|
requestBaselineMvpReview,
|
|
812
821
|
verifyRecordingMvp,
|
|
822
|
+
selectLiveComponentRecovery,
|
|
813
823
|
requestParameterSelectionAdvice,
|
|
814
824
|
requestCompletionReview,
|
|
815
825
|
compileFocusedTool: compileFocusedToolWithShippedAgent,
|
|
@@ -1642,6 +1652,7 @@ function verificationFailureProjection(
|
|
|
1642
1652
|
failure.error instanceof Error ? failure.error.message : String(failure.error),
|
|
1643
1653
|
12_000,
|
|
1644
1654
|
),
|
|
1655
|
+
componentRecoveries: failure.componentRecoveries ?? 0,
|
|
1645
1656
|
...(failure.compilerSummary
|
|
1646
1657
|
? {
|
|
1647
1658
|
compilerSummary: utf8Prefix(failure.compilerSummary, 8_000),
|
|
@@ -1990,6 +2001,7 @@ async function compileFocusedToolWithShippedAgent(input: {
|
|
|
1990
2001
|
priorToolDir?: string;
|
|
1991
2002
|
apiResearchDir?: string;
|
|
1992
2003
|
apiResearchSummary?: string;
|
|
2004
|
+
apiResearchAdvisory?: ApiResearchPlanAdvisory;
|
|
1993
2005
|
consumerResearch?: ConsumerResearchContext[];
|
|
1994
2006
|
revisionGuidance?: string;
|
|
1995
2007
|
revisionContext?: FocusedPlannerRevisionContext;
|
|
@@ -2036,14 +2048,15 @@ async function compileFocusedToolWithShippedAgent(input: {
|
|
|
2036
2048
|
...(input.apiResearchSummary
|
|
2037
2049
|
? {
|
|
2038
2050
|
apiResearchHandoff: {
|
|
2039
|
-
status: '
|
|
2051
|
+
status: 'prior_observation',
|
|
2040
2052
|
instruction:
|
|
2041
|
-
'
|
|
2053
|
+
'The seeded files and observations describe the researched construction, not proof of the current build. Use them as a starting point for the accepted implementation plan. Any plan/evidence differences below are advisory: implement the accepted construction and run the normal checks, or report a concrete contradiction for master repair. Do not attribute an older observation to changed bytes.',
|
|
2042
2054
|
summary: input.apiResearchSummary,
|
|
2043
2055
|
...(researchEvidence ? { evidenceFiles: researchEvidence } : {}),
|
|
2044
2056
|
},
|
|
2045
2057
|
}
|
|
2046
2058
|
: {}),
|
|
2059
|
+
...(input.apiResearchAdvisory ? { apiResearchAdvisory: input.apiResearchAdvisory } : {}),
|
|
2047
2060
|
...(input.revisionGuidance || input.revisionContext
|
|
2048
2061
|
? {
|
|
2049
2062
|
revision: {
|
|
@@ -2152,13 +2165,163 @@ export function apiResearchMatchesPlan(
|
|
|
2152
2165
|
);
|
|
2153
2166
|
}
|
|
2154
2167
|
|
|
2168
|
+
type ResearchPlanSource = Pick<
|
|
2169
|
+
ApiResearchHandoff,
|
|
2170
|
+
'researchInputsSha256' | 'candidate' | 'observation'
|
|
2171
|
+
>;
|
|
2172
|
+
type ResearchPlanValue = string | number | null;
|
|
2173
|
+
|
|
2174
|
+
export interface ApiResearchPlanDifference {
|
|
2175
|
+
path: string;
|
|
2176
|
+
planned: ResearchPlanValue;
|
|
2177
|
+
researched: ResearchPlanValue;
|
|
2178
|
+
/** Path inside researchRef, rather than an implied observation of the plan. */
|
|
2179
|
+
reference: string;
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
export interface ApiResearchPlanAdvisory {
|
|
2183
|
+
kind: 'api_research_plan_difference';
|
|
2184
|
+
toolId: string;
|
|
2185
|
+
toolName: string;
|
|
2186
|
+
implementationPlanRef?: ContentAddressedRef;
|
|
2187
|
+
researchRef?: ContentAddressedRef;
|
|
2188
|
+
observationId?: string;
|
|
2189
|
+
differences: ApiResearchPlanDifference[];
|
|
2190
|
+
differenceCount: number;
|
|
2191
|
+
omittedDifferenceCount: number;
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
/** Compare declared fields only. A different prior construction is context for
|
|
2195
|
+
* the agents, never permission to compile or proof of the changed construction. */
|
|
2196
|
+
export function apiResearchPlanDifferences(
|
|
2197
|
+
tool: EditableTeachingTool,
|
|
2198
|
+
implementation: ImplementationPlanPayload,
|
|
2199
|
+
research?: ResearchPlanSource,
|
|
2200
|
+
): ApiResearchPlanDifference[] {
|
|
2201
|
+
if (tool.strategy?.kind !== 'api' || implementation.strategyKind !== 'api') return [];
|
|
2202
|
+
const differences: ApiResearchPlanDifference[] = [];
|
|
2203
|
+
const compare = (
|
|
2204
|
+
path: string,
|
|
2205
|
+
planned: ResearchPlanValue | undefined,
|
|
2206
|
+
researched: ResearchPlanValue | undefined,
|
|
2207
|
+
reference: string,
|
|
2208
|
+
) => {
|
|
2209
|
+
if (planned !== researched)
|
|
2210
|
+
differences.push({
|
|
2211
|
+
path,
|
|
2212
|
+
planned: planned ?? null,
|
|
2213
|
+
researched: researched ?? null,
|
|
2214
|
+
reference,
|
|
2215
|
+
});
|
|
2216
|
+
};
|
|
2217
|
+
const workflow = research?.candidate?.workflow;
|
|
2218
|
+
if (!workflow) {
|
|
2219
|
+
compare('research.candidate', 'API implementation plan', undefined, 'candidate');
|
|
2220
|
+
return differences;
|
|
2221
|
+
}
|
|
2222
|
+
compare(
|
|
2223
|
+
'candidate.toolName',
|
|
2224
|
+
tool.candidate.toolName,
|
|
2225
|
+
workflow.toolName,
|
|
2226
|
+
'candidate.workflow.toolName',
|
|
2227
|
+
);
|
|
2228
|
+
tool.candidate.likelyParams.forEach(({ name, type }, index) => {
|
|
2229
|
+
const researchedIndex = workflow.parameters.findIndex((parameter) => parameter.name === name);
|
|
2230
|
+
compare(
|
|
2231
|
+
`candidate.likelyParams[${index}].type`,
|
|
2232
|
+
type,
|
|
2233
|
+
workflow.parameters[researchedIndex]?.type,
|
|
2234
|
+
researchedIndex < 0
|
|
2235
|
+
? 'candidate.workflow.parameters'
|
|
2236
|
+
: `candidate.workflow.parameters[${researchedIndex}].type`,
|
|
2237
|
+
);
|
|
2238
|
+
});
|
|
2239
|
+
compare(
|
|
2240
|
+
'requestProvenance.length',
|
|
2241
|
+
implementation.requestProvenance.length,
|
|
2242
|
+
workflow.requests.length,
|
|
2243
|
+
'candidate.workflow.requests.length',
|
|
2244
|
+
);
|
|
2245
|
+
const requestCount = Math.max(implementation.requestProvenance.length, workflow.requests.length);
|
|
2246
|
+
for (let index = 0; index < requestCount; index++) {
|
|
2247
|
+
const planned = implementation.requestProvenance[index];
|
|
2248
|
+
const researched = workflow.requests[index];
|
|
2249
|
+
compare(
|
|
2250
|
+
`requestProvenance[${index}].recordingRequestSeq`,
|
|
2251
|
+
planned?.recordingRequestSeq,
|
|
2252
|
+
researched?.recordingRequestSeq,
|
|
2253
|
+
`candidate.workflow.requests[${index}].recordingRequestSeq`,
|
|
2254
|
+
);
|
|
2255
|
+
compare(
|
|
2256
|
+
`requestProvenance[${index}].recordingResponseRequestSeq`,
|
|
2257
|
+
planned?.recordingResponseRequestSeq,
|
|
2258
|
+
researched?.navigation?.networkResponse?.recordingResponseRequestSeq,
|
|
2259
|
+
`candidate.workflow.requests[${index}].navigation.networkResponse.recordingResponseRequestSeq`,
|
|
2260
|
+
);
|
|
2261
|
+
}
|
|
2262
|
+
return differences;
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2265
|
+
function apiResearchPlanAdvisory(
|
|
2266
|
+
tool: EditableTeachingTool,
|
|
2267
|
+
implementation: ImplementationPlanPayload,
|
|
2268
|
+
research: ResearchPlanSource | undefined,
|
|
2269
|
+
store: (value: unknown) => ContentAddressedRef,
|
|
2270
|
+
): ApiResearchPlanAdvisory | undefined {
|
|
2271
|
+
const differences = apiResearchPlanDifferences(tool, implementation, research);
|
|
2272
|
+
if (differences.length === 0) return undefined;
|
|
2273
|
+
// Bound prompt diagnostics, not the plan, investigation, or number of attempts.
|
|
2274
|
+
const displayed = differences.slice(0, 32);
|
|
2275
|
+
return {
|
|
2276
|
+
kind: 'api_research_plan_difference',
|
|
2277
|
+
toolId: tool.id,
|
|
2278
|
+
toolName: tool.candidate.toolName,
|
|
2279
|
+
...(tool.implementationPlan ? { implementationPlanRef: tool.implementationPlan } : {}),
|
|
2280
|
+
...(research ? { researchRef: store(research) } : {}),
|
|
2281
|
+
...(research?.observation ? { observationId: research.observation.id } : {}),
|
|
2282
|
+
differences: displayed,
|
|
2283
|
+
differenceCount: differences.length,
|
|
2284
|
+
omittedDifferenceCount: differences.length - displayed.length,
|
|
2285
|
+
};
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
function plannerResearchAdvisoryProjection(input: {
|
|
2289
|
+
planners: readonly HostedPlannerBundle[];
|
|
2290
|
+
research: readonly ApiResearchHandoff[];
|
|
2291
|
+
seeds: Map<string, FreshTeachBootstrapObject>;
|
|
2292
|
+
}): PromptEvidenceProjection | undefined {
|
|
2293
|
+
const store = (value: unknown) => addBootstrap(input.seeds, jsonRef(value));
|
|
2294
|
+
const entries: PromptEvidenceEntry[] = input.planners.flatMap(({ proposal }) => {
|
|
2295
|
+
const tool = proposal.payload.tool;
|
|
2296
|
+
const advisory = apiResearchPlanAdvisory(
|
|
2297
|
+
tool,
|
|
2298
|
+
proposal.payload.implementationPlan.payload,
|
|
2299
|
+
input.research.find(({ toolName }) => toolName === tool.candidate.toolName),
|
|
2300
|
+
store,
|
|
2301
|
+
);
|
|
2302
|
+
return advisory
|
|
2303
|
+
? [
|
|
2304
|
+
{
|
|
2305
|
+
kind: 'untrusted_redacted_quote',
|
|
2306
|
+
provenance: 'plan_note',
|
|
2307
|
+
ref: store(advisory),
|
|
2308
|
+
quote: canonicalTeachingPlanJson(advisory),
|
|
2309
|
+
},
|
|
2310
|
+
]
|
|
2311
|
+
: [];
|
|
2312
|
+
});
|
|
2313
|
+
if (entries.length === 0) return undefined;
|
|
2314
|
+
const payload = { entries };
|
|
2315
|
+
return PromptEvidenceProjectionSchema.parse({ ref: store(payload), payload });
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2155
2318
|
/**
|
|
2156
2319
|
* Research is evidence for a public operation boundary, not for planner prose.
|
|
2157
2320
|
* A later plan may narrow a proven operation by fixing researched parameters to
|
|
2158
2321
|
* constants, but it may not add an unresearched public parameter or discard the
|
|
2159
2322
|
* recorded request that actually passed. Additional recording references are
|
|
2160
|
-
* context, not additional executed requests
|
|
2161
|
-
*
|
|
2323
|
+
* context, not additional executed requests. This describes reuse of the old
|
|
2324
|
+
* boundary evidence; it does not prohibit compiling a revised construction.
|
|
2162
2325
|
*/
|
|
2163
2326
|
export function apiResearchCoversToolBoundary(
|
|
2164
2327
|
tool: EditableTeachingTool,
|
|
@@ -2350,6 +2513,7 @@ type FocusedPlannerFailure = NonNullable<MasterDecisionInput['plannerFailures']>
|
|
|
2350
2513
|
|
|
2351
2514
|
async function requestFocusedPlannerBundles(input: {
|
|
2352
2515
|
onInvalidOutput?: (failure: FocusedPlannerFailure) => void;
|
|
2516
|
+
report?: (message: string) => void;
|
|
2353
2517
|
plan: EditableTeachingPlan;
|
|
2354
2518
|
discoveryRun: ToolSelectionAdvisorInput['run'];
|
|
2355
2519
|
recordingIndex: ToolSelectionAdvisorInput['recordingIndex'];
|
|
@@ -2428,7 +2592,17 @@ async function requestFocusedPlannerBundles(input: {
|
|
|
2428
2592
|
evidence,
|
|
2429
2593
|
...(revisionContext ? { revisionContext } : {}),
|
|
2430
2594
|
};
|
|
2431
|
-
|
|
2595
|
+
Object.assign(
|
|
2596
|
+
plannerInput,
|
|
2597
|
+
focusedPlannerResearchContext(plannerInput, input.agent.sharedResearch),
|
|
2598
|
+
);
|
|
2599
|
+
const output = await withTeachStageProgress(
|
|
2600
|
+
{
|
|
2601
|
+
label: `${sourceTool.candidate.toolName}: implementation planning`,
|
|
2602
|
+
report: input.report,
|
|
2603
|
+
},
|
|
2604
|
+
() => input.deps.requestFocusedPlan(plannerInput, input.agent),
|
|
2605
|
+
);
|
|
2432
2606
|
const authoredEdges = [
|
|
2433
2607
|
...input.plan.chainEdges.filter(({ consumerToolId }) => consumerToolId !== output.tool.id),
|
|
2434
2608
|
...output.chainEdges,
|
|
@@ -2775,33 +2949,90 @@ async function researchSelectedOperations(input: {
|
|
|
2775
2949
|
const targetTools = input.plan.buildWaves.flat().flatMap((id) => selectedById.get(id) ?? []);
|
|
2776
2950
|
const targetIds = new Set(targetTools.map(({ id }) => id));
|
|
2777
2951
|
const idByName = new Map(input.plan.tools.map((tool) => [tool.candidate.toolName, tool.id]));
|
|
2952
|
+
const predecessors = new Map(
|
|
2953
|
+
targetTools.map((tool) => [
|
|
2954
|
+
tool.id,
|
|
2955
|
+
new Set([
|
|
2956
|
+
...tool.candidate.dependsOnTools.flatMap((name) => idByName.get(name) ?? []),
|
|
2957
|
+
...input.plan.chainEdges
|
|
2958
|
+
.filter(({ consumerToolId }) => consumerToolId === tool.id)
|
|
2959
|
+
.map(({ producerToolId }) => producerToolId),
|
|
2960
|
+
]),
|
|
2961
|
+
]),
|
|
2962
|
+
);
|
|
2778
2963
|
const settled = new Map<string, Promise<void>>();
|
|
2964
|
+
const settledResearch = new Set<string>();
|
|
2779
2965
|
const settle = new Map<string, () => void>();
|
|
2780
2966
|
for (const tool of targetTools)
|
|
2781
2967
|
settled.set(tool.id, new Promise<void>((resolve) => settle.set(tool.id, resolve)));
|
|
2782
2968
|
const queuedResearch = new Set(targetTools.map(({ id }) => id));
|
|
2783
2969
|
const unfinishedResearch = new Set(queuedResearch);
|
|
2784
|
-
//
|
|
2785
|
-
//
|
|
2970
|
+
// Prioritize research that can actually start. A consumer waiting on its
|
|
2971
|
+
// producer must not prevent unrelated proven tools from preparing drafts.
|
|
2786
2972
|
const canDraft = () =>
|
|
2787
|
-
|
|
2973
|
+
!targetTools.some(
|
|
2974
|
+
({ id }) =>
|
|
2975
|
+
queuedResearch.has(id) &&
|
|
2976
|
+
[...(predecessors.get(id) ?? [])].every(
|
|
2977
|
+
(producer) => !targetIds.has(producer) || settledResearch.has(producer),
|
|
2978
|
+
),
|
|
2979
|
+
) &&
|
|
2788
2980
|
(unfinishedResearch.size > 0 ||
|
|
2789
2981
|
(Boolean(currentNativeTeachAgents()) &&
|
|
2790
2982
|
input.runDeadline.deadlineMs - Date.now() >= SPECULATIVE_PLANNING_MIN_REMAINING_MS));
|
|
2791
2983
|
const completedHandoffs = new Map<string, ApiResearchHandoff>();
|
|
2792
|
-
const
|
|
2984
|
+
const pendingDrafts = new Map<
|
|
2985
|
+
string,
|
|
2986
|
+
{
|
|
2987
|
+
tool: EditableTeachingTool;
|
|
2988
|
+
result: ApiResearchResult;
|
|
2989
|
+
handoff: ApiResearchHandoff;
|
|
2990
|
+
evidence: PromptEvidenceProjection;
|
|
2991
|
+
}
|
|
2992
|
+
>();
|
|
2993
|
+
const draftAdmissions: Promise<void>[] = [];
|
|
2994
|
+
let draftFailure: unknown;
|
|
2995
|
+
const admitDrafts = () => {
|
|
2996
|
+
if (!canDraft()) return;
|
|
2997
|
+
for (const [id, pending] of pendingDrafts) {
|
|
2998
|
+
// Give the master's dependent research its producer evidence first; its
|
|
2999
|
+
// findings can change that producer contract. Independent drafts can run.
|
|
3000
|
+
if (
|
|
3001
|
+
targetTools.some(
|
|
3002
|
+
(tool) => unfinishedResearch.has(tool.id) && predecessors.get(tool.id)?.has(id),
|
|
3003
|
+
)
|
|
3004
|
+
)
|
|
3005
|
+
continue;
|
|
3006
|
+
pendingDrafts.delete(id);
|
|
3007
|
+
const admission = input.onProven?.(
|
|
3008
|
+
pending.tool,
|
|
3009
|
+
pending.result,
|
|
3010
|
+
pending.handoff,
|
|
3011
|
+
pending.evidence,
|
|
3012
|
+
canDraft,
|
|
3013
|
+
input.plan,
|
|
3014
|
+
[
|
|
3015
|
+
...(input.previousHandoffs ?? []).filter(
|
|
3016
|
+
(previous) => !completedHandoffs.has(previous.toolName),
|
|
3017
|
+
),
|
|
3018
|
+
...completedHandoffs.values(),
|
|
3019
|
+
],
|
|
3020
|
+
);
|
|
3021
|
+
if (admission)
|
|
3022
|
+
draftAdmissions.push(
|
|
3023
|
+
admission.catch((error) => {
|
|
3024
|
+
draftFailure ??= error;
|
|
3025
|
+
}),
|
|
3026
|
+
);
|
|
3027
|
+
}
|
|
3028
|
+
};
|
|
3029
|
+
const researchWork = compileEveryToolInBuildWaves(
|
|
2793
3030
|
{ tools: targetTools, buildWaves: [targetTools.map(({ id }) => id)] },
|
|
2794
3031
|
{
|
|
2795
3032
|
concurrency: FOCUSED_COMPILE_CONCURRENCY,
|
|
2796
3033
|
compileTool: async (sourceTool) => {
|
|
2797
|
-
const predecessorIds = new Set([
|
|
2798
|
-
...sourceTool.candidate.dependsOnTools.flatMap((name) => idByName.get(name) ?? []),
|
|
2799
|
-
...input.plan.chainEdges
|
|
2800
|
-
.filter(({ consumerToolId }) => consumerToolId === sourceTool.id)
|
|
2801
|
-
.map(({ producerToolId }) => producerToolId),
|
|
2802
|
-
]);
|
|
2803
3034
|
await Promise.all(
|
|
2804
|
-
[...
|
|
3035
|
+
[...(predecessors.get(sourceTool.id) ?? [])]
|
|
2805
3036
|
.filter((id) => id !== sourceTool.id && targetIds.has(id))
|
|
2806
3037
|
.map((id) => settled.get(id)),
|
|
2807
3038
|
);
|
|
@@ -2840,7 +3071,7 @@ async function researchSelectedOperations(input: {
|
|
|
2840
3071
|
: `${tool.candidate.toolName}: researching the minimum viable API call`,
|
|
2841
3072
|
);
|
|
2842
3073
|
try {
|
|
2843
|
-
const
|
|
3074
|
+
const researching = researchApiMvpCall({
|
|
2844
3075
|
run: input.run,
|
|
2845
3076
|
recordingIndex: input.recordingIndex,
|
|
2846
3077
|
session: input.triagedSession,
|
|
@@ -2947,6 +3178,9 @@ async function researchSelectedOperations(input: {
|
|
|
2947
3178
|
runApiTool: input.deps.runApiResearchTool,
|
|
2948
3179
|
},
|
|
2949
3180
|
});
|
|
3181
|
+
// Offer ready research before releasing older proven drafts.
|
|
3182
|
+
admitDrafts();
|
|
3183
|
+
const outcome = await researching;
|
|
2950
3184
|
unfinishedResearch.delete(sourceTool.id);
|
|
2951
3185
|
if (isPartialApiResearch(outcome)) {
|
|
2952
3186
|
input.report?.(`${tool.candidate.toolName}: API research is partial`);
|
|
@@ -2973,14 +3207,15 @@ async function researchSelectedOperations(input: {
|
|
|
2973
3207
|
observations: outcome.observations,
|
|
2974
3208
|
});
|
|
2975
3209
|
completedHandoffs.set(handoff.toolName, handoff);
|
|
3210
|
+
settledResearch.add(sourceTool.id);
|
|
2976
3211
|
settle.get(sourceTool.id)?.();
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
3212
|
+
pendingDrafts.set(sourceTool.id, {
|
|
3213
|
+
tool: sourceTool,
|
|
3214
|
+
result: outcome,
|
|
3215
|
+
handoff,
|
|
3216
|
+
evidence,
|
|
3217
|
+
});
|
|
3218
|
+
admitDrafts();
|
|
2984
3219
|
return handoff;
|
|
2985
3220
|
} catch (error) {
|
|
2986
3221
|
unfinishedResearch.delete(sourceTool.id);
|
|
@@ -2997,11 +3232,17 @@ async function researchSelectedOperations(input: {
|
|
|
2997
3232
|
});
|
|
2998
3233
|
}
|
|
2999
3234
|
} finally {
|
|
3235
|
+
settledResearch.add(sourceTool.id);
|
|
3000
3236
|
settle.get(sourceTool.id)?.();
|
|
3237
|
+
admitDrafts();
|
|
3001
3238
|
}
|
|
3002
3239
|
},
|
|
3003
3240
|
},
|
|
3004
3241
|
);
|
|
3242
|
+
const researched = await researchWork.finally(async () => {
|
|
3243
|
+
await Promise.all(draftAdmissions);
|
|
3244
|
+
});
|
|
3245
|
+
if (draftFailure !== undefined) throw draftFailure;
|
|
3005
3246
|
if (researched.failures.length > 0) {
|
|
3006
3247
|
throwTerminalFanoutFailure(researched.failures, input.agent.signal);
|
|
3007
3248
|
throw new AggregateError(
|
|
@@ -3186,11 +3427,14 @@ async function reviewApiResearchBeforePlanning(input: {
|
|
|
3186
3427
|
apiResearch: currentHandoffs,
|
|
3187
3428
|
...(researchNoProgress.length > 0 ? { researchNoProgress } : {}),
|
|
3188
3429
|
};
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3430
|
+
const decision = await withTeachStageProgress(
|
|
3431
|
+
{ label: 'master: API research review', report: input.report },
|
|
3432
|
+
() =>
|
|
3433
|
+
input.deps.requestMasterDecision(
|
|
3434
|
+
decisionInput,
|
|
3435
|
+
input.agent,
|
|
3436
|
+
firstReview && input.selfContainedFirstReview ? { selfContained: true } : undefined,
|
|
3437
|
+
),
|
|
3194
3438
|
);
|
|
3195
3439
|
firstReview = false;
|
|
3196
3440
|
const decisionRef = addBootstrap(input.seeds, jsonRef(decision));
|
|
@@ -3412,6 +3656,7 @@ async function discoverAndPlan(input: {
|
|
|
3412
3656
|
toolAdvice: ToolAdvice;
|
|
3413
3657
|
apiResearch: ApiResearchHandoff[];
|
|
3414
3658
|
apiResearchResults: Map<string, ApiResearchResult>;
|
|
3659
|
+
researchFollowUps: ApiResearchFollowUpDirective[];
|
|
3415
3660
|
earlyDrafts: Map<
|
|
3416
3661
|
string,
|
|
3417
3662
|
{ compiled: CompiledFocusedTool; compileInputsSha256: string; implementationSha256: string }
|
|
@@ -3570,6 +3815,7 @@ async function discoverAndPlan(input: {
|
|
|
3570
3815
|
try {
|
|
3571
3816
|
const [bundle] = await requestFocusedPlannerBundles({
|
|
3572
3817
|
plan: currentPlan,
|
|
3818
|
+
report: input.report,
|
|
3573
3819
|
discoveryRun: run,
|
|
3574
3820
|
recordingIndex,
|
|
3575
3821
|
triagedSession: input.triage.session,
|
|
@@ -3583,7 +3829,7 @@ async function discoverAndPlan(input: {
|
|
|
3583
3829
|
});
|
|
3584
3830
|
if (!bundle) return;
|
|
3585
3831
|
const proposed = bundle.proposal.payload.tool;
|
|
3586
|
-
if (
|
|
3832
|
+
if (proposed.strategy?.kind !== 'api') return;
|
|
3587
3833
|
earlyPlans.set(tool.id, {
|
|
3588
3834
|
bundle,
|
|
3589
3835
|
research: result,
|
|
@@ -3593,7 +3839,6 @@ async function discoverAndPlan(input: {
|
|
|
3593
3839
|
evidenceRefs: [],
|
|
3594
3840
|
}),
|
|
3595
3841
|
});
|
|
3596
|
-
if (!canDraft()) return;
|
|
3597
3842
|
input.report?.(
|
|
3598
3843
|
`${tool.candidate.toolName}: compiling a draft while other research continues`,
|
|
3599
3844
|
);
|
|
@@ -3605,11 +3850,20 @@ async function discoverAndPlan(input: {
|
|
|
3605
3850
|
stagingDir: pathJoin(input.stagingRoot, 'research-drafts', tool.id),
|
|
3606
3851
|
apiResearchDir: result.toolDir,
|
|
3607
3852
|
apiResearchSummary: result.summary,
|
|
3853
|
+
apiResearchAdvisory: apiResearchPlanAdvisory(
|
|
3854
|
+
proposed,
|
|
3855
|
+
bundle.output.implementationPlan,
|
|
3856
|
+
result,
|
|
3857
|
+
(value) => addBootstrap(input.seeds, jsonRef(value)),
|
|
3858
|
+
),
|
|
3608
3859
|
resumeSessionId: input.compileSessionsByToolId.get(tool.id),
|
|
3609
3860
|
onSessionId: (id) => input.compileSessionsByToolId.set(tool.id, id),
|
|
3610
3861
|
runDeadline: input.runDeadline,
|
|
3611
3862
|
signal: input.signal,
|
|
3612
3863
|
});
|
|
3864
|
+
input.report?.(
|
|
3865
|
+
`${tool.candidate.toolName}: draft generation finished; normal teaching checks and publication are pending`,
|
|
3866
|
+
);
|
|
3613
3867
|
earlyDrafts.set(tool.id, {
|
|
3614
3868
|
compiled,
|
|
3615
3869
|
compileInputsSha256: bundle.proposal.payload.binding.compileInputsSha256,
|
|
@@ -3704,6 +3958,7 @@ async function discoverAndPlan(input: {
|
|
|
3704
3958
|
...reusablePlans,
|
|
3705
3959
|
...(await requestFocusedPlannerBundles({
|
|
3706
3960
|
plan: research.plan,
|
|
3961
|
+
report: input.report,
|
|
3707
3962
|
onInvalidOutput: (failure) => plannerFailures.push(failure),
|
|
3708
3963
|
discoveryRun: run,
|
|
3709
3964
|
recordingIndex,
|
|
@@ -3720,6 +3975,12 @@ async function discoverAndPlan(input: {
|
|
|
3720
3975
|
})),
|
|
3721
3976
|
];
|
|
3722
3977
|
const evidenceRefs = allEvidenceRefs(discoveryEvidence, plannerBundles);
|
|
3978
|
+
const researchAdvisories = plannerResearchAdvisoryProjection({
|
|
3979
|
+
planners: plannerBundles,
|
|
3980
|
+
research: research.handoffs,
|
|
3981
|
+
seeds: input.seeds,
|
|
3982
|
+
});
|
|
3983
|
+
if (researchAdvisories) evidenceRefs.push(...focusedEvidenceRefs(researchAdvisories));
|
|
3723
3984
|
const prePlanObject = jsonRef(research.plan);
|
|
3724
3985
|
addBootstrap(input.seeds, prePlanObject);
|
|
3725
3986
|
const revisionInput: MasterDecisionInput = {
|
|
@@ -3743,16 +4004,21 @@ async function discoverAndPlan(input: {
|
|
|
3743
4004
|
plannerProposals: plannerBundles.map(({ proposal }) => proposal),
|
|
3744
4005
|
...(plannerFailures.length ? { plannerFailures } : {}),
|
|
3745
4006
|
apiResearch: research.handoffs,
|
|
4007
|
+
...(researchAdvisories ? { verificationFindings: researchAdvisories } : {}),
|
|
3746
4008
|
};
|
|
3747
|
-
const finalDecision = await
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
4009
|
+
const finalDecision = await withTeachStageProgress(
|
|
4010
|
+
{ label: 'master: overall plan review', report: input.report },
|
|
4011
|
+
() =>
|
|
4012
|
+
input.deps.requestMasterDecision(
|
|
4013
|
+
revisionInput,
|
|
4014
|
+
input.agent,
|
|
4015
|
+
input.selected ? { selfContained: true } : undefined,
|
|
4016
|
+
),
|
|
3751
4017
|
);
|
|
3752
4018
|
const finalDecisionRef = addBootstrap(input.seeds, jsonRef(finalDecision));
|
|
3753
4019
|
const proposalRefs = plannerBundles.map(({ proposal }) => proposal.ref);
|
|
3754
4020
|
const plan = createEditableTeachingPlan(
|
|
3755
|
-
finalDecision
|
|
4021
|
+
desiredPlanAfterResearchRequest(finalDecision),
|
|
3756
4022
|
{
|
|
3757
4023
|
decision: planDecision(
|
|
3758
4024
|
input.now,
|
|
@@ -3777,20 +4043,9 @@ async function discoverAndPlan(input: {
|
|
|
3777
4043
|
discoveryCandidateNames: discoveryInput.discoveryCandidates.map(({ toolName }) => toolName),
|
|
3778
4044
|
},
|
|
3779
4045
|
);
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
const implementation =
|
|
3784
|
-
seed?.kind === 'json' ? ImplementationPlanPayloadSchema.safeParse(seed.value) : undefined;
|
|
3785
|
-
const result = research.resultsByToolId.get(tool.id);
|
|
3786
|
-
if (
|
|
3787
|
-
!implementation?.success ||
|
|
3788
|
-
!result ||
|
|
3789
|
-
!apiResearchMatchesPlan(tool, implementation.data, result)
|
|
3790
|
-
) {
|
|
3791
|
-
tool.implementationPlan = undefined;
|
|
3792
|
-
}
|
|
3793
|
-
}
|
|
4046
|
+
// The master may deliberately accept a construction different from research.
|
|
4047
|
+
// Keep its accepted plan; the journal validates refs/schema and each new build
|
|
4048
|
+
// receives its own normal checks rather than inheriting the old observation.
|
|
3794
4049
|
return {
|
|
3795
4050
|
plan,
|
|
3796
4051
|
discoveryInput,
|
|
@@ -3808,11 +4063,18 @@ async function discoverAndPlan(input: {
|
|
|
3808
4063
|
toolAdvice: advice,
|
|
3809
4064
|
apiResearch: research.handoffs,
|
|
3810
4065
|
apiResearchResults: research.resultsByToolId,
|
|
4066
|
+
researchFollowUps: finalDecision.researchFollowUps ?? [],
|
|
3811
4067
|
earlyDrafts,
|
|
3812
4068
|
};
|
|
3813
4069
|
}
|
|
3814
4070
|
|
|
3815
4071
|
interface CheckedBuilds {
|
|
4072
|
+
componentRepairs: Array<{
|
|
4073
|
+
toolId: string;
|
|
4074
|
+
componentId: string;
|
|
4075
|
+
sourceToolId: string;
|
|
4076
|
+
reason: string;
|
|
4077
|
+
}>;
|
|
3816
4078
|
compiledByToolId: Map<string, CompiledFocusedTool>;
|
|
3817
4079
|
revisionSourceByToolId: Map<string, CompiledFocusedTool>;
|
|
3818
4080
|
draftSourceByToolStrategy: Map<string, CompiledFocusedTool>;
|
|
@@ -3859,6 +4121,7 @@ function checkFailure(
|
|
|
3859
4121
|
| 'chainEdgeIds'
|
|
3860
4122
|
| 'compilerSummary'
|
|
3861
4123
|
| 'liveResponseObservations'
|
|
4124
|
+
| 'componentRecoveries'
|
|
3862
4125
|
> = {},
|
|
3863
4126
|
): BuildWaveFailure {
|
|
3864
4127
|
return {
|
|
@@ -4124,6 +4387,7 @@ function receiptPassed(facts: readonly ReceiptFact[]): boolean {
|
|
|
4124
4387
|
}
|
|
4125
4388
|
|
|
4126
4389
|
async function compileAndCheckCurrentPlan(input: {
|
|
4390
|
+
componentRecoveryOwners?: readonly ComponentRecoveryOwner[];
|
|
4127
4391
|
journal: FreshTeachJournal;
|
|
4128
4392
|
triage: TriageResult;
|
|
4129
4393
|
sessionPath: string;
|
|
@@ -4173,6 +4437,7 @@ async function compileAndCheckCurrentPlan(input: {
|
|
|
4173
4437
|
}): Promise<CheckedBuilds> {
|
|
4174
4438
|
const plan = input.journal.currentPlan();
|
|
4175
4439
|
const initialState = input.journal.readState();
|
|
4440
|
+
const componentRecoveryQueue = new ComponentRecoveryQueue();
|
|
4176
4441
|
const initialBuildRefs = new Map(
|
|
4177
4442
|
initialState.tools.flatMap(({ toolId, buildRef }) => (buildRef ? [[toolId, buildRef]] : [])),
|
|
4178
4443
|
);
|
|
@@ -4285,15 +4550,15 @@ async function compileAndCheckCurrentPlan(input: {
|
|
|
4285
4550
|
);
|
|
4286
4551
|
return early.compiled;
|
|
4287
4552
|
}
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4553
|
+
const apiResearch =
|
|
4554
|
+
tool.strategy.kind === 'api' ? input.apiResearchByToolId?.get(tool.id) : undefined;
|
|
4555
|
+
const researchAdvisory = apiResearchPlanAdvisory(tool, implementation, apiResearch, (value) =>
|
|
4556
|
+
input.journal.storeJson(value),
|
|
4557
|
+
);
|
|
4558
|
+
if (researchAdvisory)
|
|
4559
|
+
input.report?.(
|
|
4560
|
+
`${tool.candidate.toolName}: ${researchAdvisory.differenceCount} advisory plan/research difference(s); compiling the accepted construction with fresh checks`,
|
|
4561
|
+
);
|
|
4297
4562
|
// A master revision gets new bytes at a new module path. This keeps a
|
|
4298
4563
|
// failed parser or transform from leaking into the next compile and also
|
|
4299
4564
|
// avoids Bun's local TypeScript module cache returning the prior build.
|
|
@@ -4315,6 +4580,7 @@ async function compileAndCheckCurrentPlan(input: {
|
|
|
4315
4580
|
(tool.strategy.kind === 'api' ? early?.compiled.toolDir : undefined)),
|
|
4316
4581
|
apiResearchDir: apiResearch?.toolDir,
|
|
4317
4582
|
apiResearchSummary: apiResearch?.summary,
|
|
4583
|
+
apiResearchAdvisory: researchAdvisory,
|
|
4318
4584
|
consumerResearch: consumerResearchForCompiler(tool.id, plan, input.apiResearchByToolId),
|
|
4319
4585
|
revisionGuidance: input.revisionGuidanceByToolId?.get(tool.id),
|
|
4320
4586
|
revisionContext: input.revisionContextByToolId?.get(tool.id),
|
|
@@ -4526,6 +4792,73 @@ async function compileAndCheckCurrentPlan(input: {
|
|
|
4526
4792
|
return unavailable;
|
|
4527
4793
|
};
|
|
4528
4794
|
|
|
4795
|
+
const componentRepairs: CheckedBuilds['componentRepairs'] = [];
|
|
4796
|
+
const selectRecovery = async (
|
|
4797
|
+
tool: EditableTeachingTool,
|
|
4798
|
+
parameters: Record<string, string | number | boolean>,
|
|
4799
|
+
failed: { result: ToolResult<unknown> },
|
|
4800
|
+
previousRecoveries: number,
|
|
4801
|
+
phase = 'live',
|
|
4802
|
+
) => {
|
|
4803
|
+
if (failed.result.ok) return undefined;
|
|
4804
|
+
input.report?.(`checking component recovery for ${tool.candidate.toolName}`);
|
|
4805
|
+
const context = tool.compileContext as SharedCompileContext;
|
|
4806
|
+
const liveInvocation = phase === 'live' || phase === 'chain';
|
|
4807
|
+
const owners: ComponentRecoveryOwner[] = liveInvocation
|
|
4808
|
+
? [...(input.componentRecoveryOwners ?? [])]
|
|
4809
|
+
: [];
|
|
4810
|
+
const ownerToolIds = new Set([tool.id]);
|
|
4811
|
+
// Walk only the accepted dependency graph, including transitive producers.
|
|
4812
|
+
for (const id of ownerToolIds) {
|
|
4813
|
+
const dependency = plan.tools.find((candidate) => candidate.id === id);
|
|
4814
|
+
if (dependency)
|
|
4815
|
+
for (const producer of declaredProducerIdsFor(dependency)) ownerToolIds.add(producer);
|
|
4816
|
+
}
|
|
4817
|
+
for (const id of ownerToolIds) {
|
|
4818
|
+
const target = plan.tools.find((candidate) => candidate.id === id);
|
|
4819
|
+
if (!target?.implementationPlan || !target.strategy) continue;
|
|
4820
|
+
owners.push({
|
|
4821
|
+
id: `tool:${id}`,
|
|
4822
|
+
kind: id === tool.id ? 'artifact' : 'prerequisite',
|
|
4823
|
+
label: target.candidate.toolName,
|
|
4824
|
+
toolIds: [tool.id],
|
|
4825
|
+
recover: async ({ reason }) => {
|
|
4826
|
+
if (!componentRepairs.some((repair) => repair.toolId === id))
|
|
4827
|
+
componentRepairs.push({
|
|
4828
|
+
toolId: id,
|
|
4829
|
+
componentId: `tool:${id}`,
|
|
4830
|
+
sourceToolId: tool.id,
|
|
4831
|
+
reason,
|
|
4832
|
+
});
|
|
4833
|
+
return false; // Its retained compiler runs before the caller is retried.
|
|
4834
|
+
},
|
|
4835
|
+
});
|
|
4836
|
+
}
|
|
4837
|
+
return input.deps.selectLiveComponentRecovery({
|
|
4838
|
+
toolId: tool.id,
|
|
4839
|
+
toolName: tool.candidate.toolName,
|
|
4840
|
+
phase,
|
|
4841
|
+
allowAuthentication: liveInvocation,
|
|
4842
|
+
parameters,
|
|
4843
|
+
owners,
|
|
4844
|
+
failure: {
|
|
4845
|
+
error: failed.result.error,
|
|
4846
|
+
status: failed.result.status,
|
|
4847
|
+
message: utf8Prefix(failed.result.message, 2_000),
|
|
4848
|
+
missing: failed.result.missing,
|
|
4849
|
+
requestStageFacts: failed.result.requestStageFacts,
|
|
4850
|
+
},
|
|
4851
|
+
previousRecoveries,
|
|
4852
|
+
recordedAuthentication: {
|
|
4853
|
+
credentialRequestSeqs: context.loginRequestSeqs,
|
|
4854
|
+
authRequestSeqs: context.authRequestSeqs,
|
|
4855
|
+
credentialNames: context.credentialNames,
|
|
4856
|
+
notes: context.authNotes,
|
|
4857
|
+
},
|
|
4858
|
+
agent: { ...input.agent, signal: input.signal ?? input.agent.signal },
|
|
4859
|
+
});
|
|
4860
|
+
};
|
|
4861
|
+
|
|
4529
4862
|
const runStandaloneCase = async (
|
|
4530
4863
|
tool: EditableTeachingTool,
|
|
4531
4864
|
focused: CompiledFocusedTool,
|
|
@@ -4539,19 +4872,52 @@ async function compileAndCheckCurrentPlan(input: {
|
|
|
4539
4872
|
.filter(({ check }) => check === 'live')
|
|
4540
4873
|
.findIndex(({ id }) => id === verification.id);
|
|
4541
4874
|
let observed: UnboundLiveCheckResult;
|
|
4875
|
+
let lastReceipt: ReturnType<FreshTeachJournal['issueReceipt']> | undefined;
|
|
4876
|
+
let completedComponentRecoveries = 0;
|
|
4542
4877
|
const apiResearch = input.apiResearchByToolId?.get(tool.id);
|
|
4878
|
+
const invocationParameters = structuredClone(
|
|
4879
|
+
parameters ?? verificationParameters(verification),
|
|
4880
|
+
);
|
|
4543
4881
|
try {
|
|
4544
|
-
observed = await
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4882
|
+
observed = await runWithComponentRecovery({
|
|
4883
|
+
queue: componentRecoveryQueue,
|
|
4884
|
+
invoke: () =>
|
|
4885
|
+
runLiveCheck({
|
|
4886
|
+
tool,
|
|
4887
|
+
compiled: focused,
|
|
4888
|
+
implementation,
|
|
4889
|
+
deps: input.deps,
|
|
4890
|
+
runDeadline: input.runDeadline,
|
|
4891
|
+
signal: input.signal,
|
|
4892
|
+
maxDurationMs: input.maxDurationMs,
|
|
4893
|
+
apiResearch,
|
|
4894
|
+
verification,
|
|
4895
|
+
parameters: structuredClone(invocationParameters),
|
|
4896
|
+
}),
|
|
4897
|
+
select: (failed, previousRecoveries) =>
|
|
4898
|
+
selectRecovery(tool, invocationParameters, failed, previousRecoveries),
|
|
4899
|
+
onAttempt: (attempt, previousRecoveries) => {
|
|
4900
|
+
completedComponentRecoveries = previousRecoveries;
|
|
4901
|
+
const directory = pathJoin(focused.toolDir, '.live-checks');
|
|
4902
|
+
mkdirSync(directory, { recursive: true });
|
|
4903
|
+
writeJsonAtomic(pathJoin(directory, `${verification.id}-${randomUUID()}.json`), {
|
|
4904
|
+
...attempt,
|
|
4905
|
+
componentRecoveries: previousRecoveries,
|
|
4906
|
+
});
|
|
4907
|
+
lastReceipt = input.journal.issueReceipt({
|
|
4908
|
+
toolId: tool.id,
|
|
4909
|
+
check: 'live',
|
|
4910
|
+
...(primary ? {} : { verificationCaseId: verification.id }),
|
|
4911
|
+
facts: invocationOutcomeCheck({
|
|
4912
|
+
subject: 'live',
|
|
4913
|
+
invocationIndex,
|
|
4914
|
+
outcome: { kind: 'returned', result: attempt.result },
|
|
4915
|
+
durationMs: attempt.durationMs,
|
|
4916
|
+
executionMechanism: attempt.executionMechanism,
|
|
4917
|
+
}).facts,
|
|
4918
|
+
});
|
|
4919
|
+
},
|
|
4550
4920
|
signal: input.signal,
|
|
4551
|
-
maxDurationMs: input.maxDurationMs,
|
|
4552
|
-
apiResearch,
|
|
4553
|
-
verification,
|
|
4554
|
-
parameters,
|
|
4555
4921
|
});
|
|
4556
4922
|
} catch (error) {
|
|
4557
4923
|
const check = invocationOutcomeCheck({
|
|
@@ -4577,10 +4943,6 @@ async function compileAndCheckCurrentPlan(input: {
|
|
|
4577
4943
|
return undefined;
|
|
4578
4944
|
}
|
|
4579
4945
|
|
|
4580
|
-
const liveEvidenceDir = pathJoin(focused.toolDir, '.live-checks');
|
|
4581
|
-
mkdirSync(liveEvidenceDir, { recursive: true });
|
|
4582
|
-
writeJsonAtomic(pathJoin(liveEvidenceDir, `${verification.id}-${randomUUID()}.json`), observed);
|
|
4583
|
-
|
|
4584
4946
|
const check = invocationOutcomeCheck({
|
|
4585
4947
|
subject: 'live',
|
|
4586
4948
|
invocationIndex,
|
|
@@ -4588,12 +4950,8 @@ async function compileAndCheckCurrentPlan(input: {
|
|
|
4588
4950
|
durationMs: observed.durationMs,
|
|
4589
4951
|
executionMechanism: observed.executionMechanism,
|
|
4590
4952
|
});
|
|
4591
|
-
const receipt =
|
|
4592
|
-
|
|
4593
|
-
check: 'live',
|
|
4594
|
-
...(primary ? {} : { verificationCaseId: verification.id }),
|
|
4595
|
-
facts: check.facts,
|
|
4596
|
-
});
|
|
4953
|
+
const receipt = lastReceipt;
|
|
4954
|
+
if (!receipt) throw new Error('Live invocation completed without an attempt receipt');
|
|
4597
4955
|
if (!receiptPassed(check.facts)) {
|
|
4598
4956
|
failures.push(
|
|
4599
4957
|
checkFailure(
|
|
@@ -4611,6 +4969,7 @@ async function compileAndCheckCurrentPlan(input: {
|
|
|
4611
4969
|
receiptRef: receipt.ref,
|
|
4612
4970
|
compilerSummary: focused.compilerSummary,
|
|
4613
4971
|
liveResponseObservations: observed.responseObservations,
|
|
4972
|
+
componentRecoveries: completedComponentRecoveries,
|
|
4614
4973
|
},
|
|
4615
4974
|
),
|
|
4616
4975
|
);
|
|
@@ -4732,6 +5091,8 @@ async function compileAndCheckCurrentPlan(input: {
|
|
|
4732
5091
|
if (new Set(edges.map(({ consumerParameter }) => consumerParameter)).size !== edges.length)
|
|
4733
5092
|
throw new Error(`chain check for "${tool.id}" repeated a consumer parameter`);
|
|
4734
5093
|
input.report?.(`checking chain ${edges.map(({ id }) => id).join(', ')}`);
|
|
5094
|
+
let returnedReceipts: Array<ReturnType<FreshTeachJournal['issueReceipt']>> | undefined;
|
|
5095
|
+
let completedComponentRecoveries = 0;
|
|
4735
5096
|
let outcome:
|
|
4736
5097
|
| {
|
|
4737
5098
|
kind: 'returned';
|
|
@@ -4877,38 +5238,80 @@ async function compileAndCheckCurrentPlan(input: {
|
|
|
4877
5238
|
};
|
|
4878
5239
|
} else {
|
|
4879
5240
|
try {
|
|
4880
|
-
|
|
5241
|
+
let attemptStartedAt = Date.now();
|
|
5242
|
+
let attemptDurationMs = 0;
|
|
4881
5243
|
const apiResearch = input.apiResearchByToolId?.get(tool.id);
|
|
4882
5244
|
let rawResponses: unknown[] | undefined;
|
|
4883
|
-
const
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
5245
|
+
const invocationParameters = structuredClone(parameters);
|
|
5246
|
+
const chained = await runWithComponentRecovery({
|
|
5247
|
+
queue: componentRecoveryQueue,
|
|
5248
|
+
invoke: async () => {
|
|
5249
|
+
attemptStartedAt = Date.now();
|
|
5250
|
+
rawResponses = undefined;
|
|
5251
|
+
return tool.strategy?.kind === 'playbook_fallback'
|
|
5252
|
+
? await runPlaybookToolCheck({
|
|
5253
|
+
deps: input.deps,
|
|
5254
|
+
playbookPath: pathJoin(focused.toolDir, 'playbook.yaml'),
|
|
5255
|
+
site: focused.workflow.site,
|
|
5256
|
+
parameters: structuredClone(invocationParameters),
|
|
5257
|
+
runDeadline: input.runDeadline,
|
|
5258
|
+
signal: input.signal,
|
|
5259
|
+
maxDurationMs: input.maxDurationMs,
|
|
5260
|
+
label: `chain check "${edges.map(({ id }) => id).join(', ')}"`,
|
|
5261
|
+
})
|
|
5262
|
+
: await runApiToolCheck({
|
|
5263
|
+
deps: input.deps,
|
|
5264
|
+
runDeadline: input.runDeadline,
|
|
5265
|
+
label: `chain check "${edges.map(({ id }) => id).join(', ')}"`,
|
|
5266
|
+
workflowPath: focused.workflowPath,
|
|
5267
|
+
onRawResponses: (responses) => {
|
|
5268
|
+
rawResponses = responses;
|
|
5269
|
+
},
|
|
5270
|
+
parameters: structuredClone(invocationParameters),
|
|
5271
|
+
...(apiResearch?.backend ? { backend: apiResearch.backend } : {}),
|
|
5272
|
+
signal: input.signal,
|
|
5273
|
+
});
|
|
5274
|
+
},
|
|
5275
|
+
select: (failed, previousRecoveries) =>
|
|
5276
|
+
selectRecovery(tool, invocationParameters, failed, previousRecoveries),
|
|
5277
|
+
onAttempt: (attempt, previousRecoveries) => {
|
|
5278
|
+
completedComponentRecoveries = previousRecoveries;
|
|
5279
|
+
attemptDurationMs = Date.now() - attemptStartedAt;
|
|
5280
|
+
const directory = pathJoin(focused.toolDir, '.live-checks');
|
|
5281
|
+
mkdirSync(directory, { recursive: true });
|
|
5282
|
+
writeJsonAtomic(pathJoin(directory, `chain-${randomUUID()}.json`), {
|
|
5283
|
+
kind: 'returned',
|
|
5284
|
+
result: attempt.result,
|
|
5285
|
+
mechanism: attempt.executionMechanism,
|
|
5286
|
+
backendAttempts: attempt.backendAttempts,
|
|
5287
|
+
rawResponses,
|
|
5288
|
+
durationMs: attemptDurationMs,
|
|
5289
|
+
componentRecoveries: previousRecoveries,
|
|
5290
|
+
parameters: invocationParameters,
|
|
5291
|
+
});
|
|
5292
|
+
const check = invocationOutcomeCheck({
|
|
5293
|
+
subject: 'chain',
|
|
5294
|
+
invocationIndex: 0,
|
|
5295
|
+
outcome: { kind: 'returned', result: attempt.result },
|
|
5296
|
+
durationMs: attemptDurationMs,
|
|
5297
|
+
executionMechanism: attempt.executionMechanism,
|
|
5298
|
+
});
|
|
5299
|
+
returnedReceipts = edges.map((edge) =>
|
|
5300
|
+
input.journal.issueReceipt({
|
|
5301
|
+
toolId: tool.id,
|
|
5302
|
+
check: 'chain',
|
|
5303
|
+
chainEdgeId: edge.id,
|
|
5304
|
+
facts: check.facts,
|
|
5305
|
+
}),
|
|
5306
|
+
);
|
|
5307
|
+
},
|
|
5308
|
+
signal: input.signal,
|
|
5309
|
+
});
|
|
4907
5310
|
outcome = {
|
|
4908
5311
|
kind: 'returned',
|
|
4909
5312
|
rawResponses,
|
|
4910
5313
|
result: chained.result,
|
|
4911
|
-
durationMs:
|
|
5314
|
+
durationMs: attemptDurationMs,
|
|
4912
5315
|
mechanism: chained.executionMechanism,
|
|
4913
5316
|
backendAttempts: chained.backendAttempts,
|
|
4914
5317
|
responseObservations:
|
|
@@ -4924,7 +5327,8 @@ async function compileAndCheckCurrentPlan(input: {
|
|
|
4924
5327
|
|
|
4925
5328
|
const chainEvidenceDir = pathJoin(focused.toolDir, '.live-checks');
|
|
4926
5329
|
mkdirSync(chainEvidenceDir, { recursive: true });
|
|
4927
|
-
|
|
5330
|
+
if (outcome.kind !== 'returned')
|
|
5331
|
+
writeJsonAtomic(pathJoin(chainEvidenceDir, `chain-${randomUUID()}.json`), outcome);
|
|
4928
5332
|
|
|
4929
5333
|
const check = invocationOutcomeCheck({
|
|
4930
5334
|
subject: 'chain',
|
|
@@ -4949,14 +5353,16 @@ async function compileAndCheckCurrentPlan(input: {
|
|
|
4949
5353
|
// A binding failure proves only the exact edge that failed. Once every
|
|
4950
5354
|
// binding succeeds, the consumer call factually exercises the whole group.
|
|
4951
5355
|
const checkedEdges = bindingFailure ? [bindingFailure.edge] : edges;
|
|
4952
|
-
const receipts = checkedEdges.map((edge) => ({
|
|
5356
|
+
const receipts = checkedEdges.map((edge, index) => ({
|
|
4953
5357
|
edge,
|
|
4954
|
-
receipt:
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
5358
|
+
receipt:
|
|
5359
|
+
(outcome.kind === 'returned' && returnedReceipts?.[index]) ||
|
|
5360
|
+
input.journal.issueReceipt({
|
|
5361
|
+
toolId: tool.id,
|
|
5362
|
+
check: 'chain',
|
|
5363
|
+
chainEdgeId: edge.id,
|
|
5364
|
+
facts: check.facts,
|
|
5365
|
+
}),
|
|
4960
5366
|
}));
|
|
4961
5367
|
const primary = receipts[0];
|
|
4962
5368
|
if (!primary) throw new Error(`chain check for "${tool.id}" issued no receipts`);
|
|
@@ -4978,6 +5384,7 @@ async function compileAndCheckCurrentPlan(input: {
|
|
|
4978
5384
|
compilerSummary: focused.compilerSummary,
|
|
4979
5385
|
liveResponseObservations:
|
|
4980
5386
|
outcome.kind === 'returned' ? outcome.responseObservations : undefined,
|
|
5387
|
+
componentRecoveries: completedComponentRecoveries,
|
|
4981
5388
|
...(bindingFailure
|
|
4982
5389
|
? { chainEdgeId: bindingFailure.edge.id }
|
|
4983
5390
|
: { chainEdgeIds: edges.map(({ id }) => id) }),
|
|
@@ -5357,6 +5764,50 @@ async function compileAndCheckCurrentPlan(input: {
|
|
|
5357
5764
|
}
|
|
5358
5765
|
}
|
|
5359
5766
|
|
|
5767
|
+
for (const failure of failures) {
|
|
5768
|
+
if (
|
|
5769
|
+
componentRepairs.some(({ sourceToolId }) => sourceToolId === failure.toolId) ||
|
|
5770
|
+
['live', 'chain'].includes(failure.stage) ||
|
|
5771
|
+
['cancelled', 'provider_unavailable'].includes(
|
|
5772
|
+
terminalStatusForError(failure.error, input.signal),
|
|
5773
|
+
)
|
|
5774
|
+
)
|
|
5775
|
+
continue;
|
|
5776
|
+
const tool = plan.tools.find((tool) => tool.id === failure.toolId);
|
|
5777
|
+
if (!tool) continue;
|
|
5778
|
+
try {
|
|
5779
|
+
const selection = await selectRecovery(
|
|
5780
|
+
tool,
|
|
5781
|
+
{},
|
|
5782
|
+
{
|
|
5783
|
+
result: {
|
|
5784
|
+
ok: false,
|
|
5785
|
+
error: 'UNKNOWN',
|
|
5786
|
+
message: failure.error instanceof Error ? failure.error.message : String(failure.error),
|
|
5787
|
+
},
|
|
5788
|
+
},
|
|
5789
|
+
0,
|
|
5790
|
+
failure.stage,
|
|
5791
|
+
);
|
|
5792
|
+
if (selection)
|
|
5793
|
+
await selection.owner.recover({
|
|
5794
|
+
failure: { ok: false, error: 'UNKNOWN', message: String(failure.error) },
|
|
5795
|
+
previousRecoveries: 0,
|
|
5796
|
+
reason: selection.reason,
|
|
5797
|
+
});
|
|
5798
|
+
} catch (error) {
|
|
5799
|
+
if (
|
|
5800
|
+
['cancelled', 'provider_unavailable'].includes(terminalStatusForError(error, input.signal))
|
|
5801
|
+
)
|
|
5802
|
+
throw error;
|
|
5803
|
+
// Preserve the original construction failure for master review if the
|
|
5804
|
+
// focused role cannot produce a usable owner decision.
|
|
5805
|
+
input.report?.(
|
|
5806
|
+
`component recovery could not resolve ${tool.candidate.toolName}; returning its failure to the master`,
|
|
5807
|
+
);
|
|
5808
|
+
}
|
|
5809
|
+
}
|
|
5810
|
+
|
|
5360
5811
|
return {
|
|
5361
5812
|
compiledByToolId,
|
|
5362
5813
|
revisionSourceByToolId,
|
|
@@ -5366,6 +5817,7 @@ async function compileAndCheckCurrentPlan(input: {
|
|
|
5366
5817
|
chainByEdgeId,
|
|
5367
5818
|
verifiedToolIds,
|
|
5368
5819
|
failures,
|
|
5820
|
+
componentRepairs,
|
|
5369
5821
|
};
|
|
5370
5822
|
}
|
|
5371
5823
|
|
|
@@ -5697,6 +6149,7 @@ async function ensureCurrentImplementationPlans(
|
|
|
5697
6149
|
const plannerFailures: FocusedPlannerFailure[] = [];
|
|
5698
6150
|
const planners = await requestFocusedPlannerBundles({
|
|
5699
6151
|
plan: current.plan,
|
|
6152
|
+
report: context.report,
|
|
5700
6153
|
onInvalidOutput: (failure) => plannerFailures.push(failure),
|
|
5701
6154
|
discoveryRun: context.discoveryInput.run,
|
|
5702
6155
|
recordingIndex: context.discoveryInput.recordingIndex,
|
|
@@ -5721,6 +6174,11 @@ async function ensureCurrentImplementationPlans(
|
|
|
5721
6174
|
);
|
|
5722
6175
|
}
|
|
5723
6176
|
reviewedProposalStates.add(proposalStateSha256);
|
|
6177
|
+
const researchAdvisories = plannerResearchAdvisoryProjection({
|
|
6178
|
+
planners,
|
|
6179
|
+
research: context.apiResearch,
|
|
6180
|
+
seeds,
|
|
6181
|
+
});
|
|
5724
6182
|
persistSeeds(context.journal, seeds);
|
|
5725
6183
|
for (const planner of planners) {
|
|
5726
6184
|
context.focusedEvidence.set(planner.output.tool.id, planner.evidence);
|
|
@@ -5728,6 +6186,7 @@ async function ensureCurrentImplementationPlans(
|
|
|
5728
6186
|
const evidenceRefs = uniqueRefs([
|
|
5729
6187
|
...revisionEvidenceRefs(context),
|
|
5730
6188
|
...allEvidenceRefs(context.discoveryEvidence, planners),
|
|
6189
|
+
...(researchAdvisories ? focusedEvidenceRefs(researchAdvisories) : []),
|
|
5731
6190
|
]);
|
|
5732
6191
|
const decisionInput: MasterDecisionInput = {
|
|
5733
6192
|
phase: 'revision',
|
|
@@ -5741,8 +6200,18 @@ async function ensureCurrentImplementationPlans(
|
|
|
5741
6200
|
plannerProposals: planners.map(({ proposal }) => proposal),
|
|
5742
6201
|
...(plannerFailures.length ? { plannerFailures } : {}),
|
|
5743
6202
|
apiResearch: [...context.apiResearch],
|
|
6203
|
+
...(researchAdvisories ? { verificationFindings: researchAdvisories } : {}),
|
|
5744
6204
|
};
|
|
5745
|
-
const
|
|
6205
|
+
const toolNames = missingToolIds.map(
|
|
6206
|
+
(id) => current.plan.tools.find((tool) => tool.id === id)?.candidate.toolName ?? id,
|
|
6207
|
+
);
|
|
6208
|
+
const decision = await withTeachStageProgress(
|
|
6209
|
+
{
|
|
6210
|
+
label: `master: implementation plan review for ${toolNames.join(', ')}`,
|
|
6211
|
+
report: context.report,
|
|
6212
|
+
},
|
|
6213
|
+
() => context.deps.requestMasterDecision(decisionInput, context.agent),
|
|
6214
|
+
);
|
|
5746
6215
|
const decisionRef = context.journal.storeJson(decision);
|
|
5747
6216
|
context.journal.revisePlan(desiredPlanAfterResearchRequest(decision), {
|
|
5748
6217
|
expectedRevision: current.plan.revision,
|
|
@@ -6406,6 +6875,11 @@ export async function runFreshMasterTeach(
|
|
|
6406
6875
|
opts: FreshTeachOptions,
|
|
6407
6876
|
overrides: Partial<FreshTeachControllerDependencies> = {},
|
|
6408
6877
|
): Promise<FreshTeachTerminalResult> {
|
|
6878
|
+
if (!currentUserInteraction()) {
|
|
6879
|
+
return await withUserInteraction(new UserInteraction(opts.onPrompt, opts.signal), () =>
|
|
6880
|
+
runFreshMasterTeach(opts, overrides),
|
|
6881
|
+
);
|
|
6882
|
+
}
|
|
6409
6883
|
const deps = { ...defaultDependencies, ...overrides };
|
|
6410
6884
|
const site = assertSiteName(opts.site);
|
|
6411
6885
|
const runId = deps.runId();
|
|
@@ -6416,6 +6890,7 @@ export async function runFreshMasterTeach(
|
|
|
6416
6890
|
const deadline = opts.unbounded
|
|
6417
6891
|
? new RollingRunDeadline()
|
|
6418
6892
|
: new RunDeadline(Date.now() + (opts.maxDurationMs ?? 12 * 60 * 60_000));
|
|
6893
|
+
currentUserInteraction()?.setRunControl(deadline);
|
|
6419
6894
|
const work = () =>
|
|
6420
6895
|
runScheduledFreshTeach(opts, deps, { site, runId, runRoot, stagingRoot, deadline });
|
|
6421
6896
|
if (providerForFreshTeach(opts) !== 'codex-cli') return await work();
|
|
@@ -6466,6 +6941,16 @@ async function runScheduledFreshTeach(
|
|
|
6466
6941
|
reportProgress(opts, 'resolving the latest recording');
|
|
6467
6942
|
const recording = await resolveRecordingForFreshRun(opts, site, deps);
|
|
6468
6943
|
const redacted = redactRecording(recording, runRoot);
|
|
6944
|
+
attachRecordedAuthentication({
|
|
6945
|
+
session: redacted.session,
|
|
6946
|
+
sessionPath: redacted.path,
|
|
6947
|
+
root: pathJoin(runRoot, 'authentication'),
|
|
6948
|
+
values: redacted.credentialValues,
|
|
6949
|
+
llmConfig: llmOptions(opts),
|
|
6950
|
+
runDeadline: deadline,
|
|
6951
|
+
compile: deps.compileAuthTool,
|
|
6952
|
+
report: (message) => reportProgress(opts, message),
|
|
6953
|
+
});
|
|
6469
6954
|
const runApiResearchTool = deps.runApiResearchTool;
|
|
6470
6955
|
deps.runApiResearchTool = async (input) => ({
|
|
6471
6956
|
...(await runApiResearchTool(input)),
|
|
@@ -6661,6 +7146,7 @@ async function runScheduledFreshTeach(
|
|
|
6661
7146
|
>
|
|
6662
7147
|
>();
|
|
6663
7148
|
const attemptedRepairStates = new Set<string>();
|
|
7149
|
+
const attemptedComponentRepairStates = new Set<string>();
|
|
6664
7150
|
const semanticReviewHistory: NonNullable<CompletionReviewInput['semanticReviewHistory']> = [];
|
|
6665
7151
|
const revisionContext = (): MasterRevisionContext => ({
|
|
6666
7152
|
journal: activeJournal,
|
|
@@ -6709,6 +7195,7 @@ async function runScheduledFreshTeach(
|
|
|
6709
7195
|
plannedTools = activeJournal.currentPlan().tools.length;
|
|
6710
7196
|
};
|
|
6711
7197
|
|
|
7198
|
+
let pendingPlanningResearch = planned.researchFollowUps;
|
|
6712
7199
|
while (true) {
|
|
6713
7200
|
const currentPlan = activeJournal.currentPlan();
|
|
6714
7201
|
plannedTools = currentPlan.tools.length;
|
|
@@ -6738,10 +7225,12 @@ async function runScheduledFreshTeach(
|
|
|
6738
7225
|
continue;
|
|
6739
7226
|
}
|
|
6740
7227
|
|
|
6741
|
-
await ensureCurrentImplementationPlans(revisionContext());
|
|
7228
|
+
await ensureCurrentImplementationPlans(revisionContext(), pendingPlanningResearch);
|
|
7229
|
+
pendingPlanningResearch = [];
|
|
6742
7230
|
let checked: CheckedBuilds;
|
|
6743
7231
|
try {
|
|
6744
7232
|
checked = await compileAndCheckCurrentPlan({
|
|
7233
|
+
componentRecoveryOwners: opts.componentRecoveryOwners,
|
|
6745
7234
|
journal: activeJournal,
|
|
6746
7235
|
triage: fullScope,
|
|
6747
7236
|
sessionPath: redacted.path,
|
|
@@ -7004,6 +7493,60 @@ async function runScheduledFreshTeach(
|
|
|
7004
7493
|
currentFailures,
|
|
7005
7494
|
latestFailureEvidenceByToolName,
|
|
7006
7495
|
);
|
|
7496
|
+
const componentRepairs = checked.componentRepairs;
|
|
7497
|
+
const componentState = `${repairStateSha256(activeJournal, currentFailures)}:${componentRepairs
|
|
7498
|
+
.map(({ componentId }) => componentId)
|
|
7499
|
+
.sort()
|
|
7500
|
+
.join(',')}`;
|
|
7501
|
+
if (componentRepairs.length > 0 && !attemptedComponentRepairStates.has(componentState)) {
|
|
7502
|
+
attemptedComponentRepairStates.add(componentState);
|
|
7503
|
+
const plan = activeJournal.currentPlan();
|
|
7504
|
+
const { version: _version, revision, decision: _decision, ...desired } = plan;
|
|
7505
|
+
const targetIds = [...new Set(componentRepairs.map(({ toolId }) => toolId))];
|
|
7506
|
+
const repairRef = activeJournal.storeJson({
|
|
7507
|
+
type: 'component_repair',
|
|
7508
|
+
requests: componentRepairs,
|
|
7509
|
+
failureEvidenceRef: currentFailures.ref,
|
|
7510
|
+
});
|
|
7511
|
+
const focusedFailuresByToolId = new Map(
|
|
7512
|
+
targetIds.map((toolId) => {
|
|
7513
|
+
const requests = componentRepairs.filter((repair) => repair.toolId === toolId);
|
|
7514
|
+
const relevantToolIds = new Set([
|
|
7515
|
+
toolId,
|
|
7516
|
+
...requests.map(({ sourceToolId }) => sourceToolId),
|
|
7517
|
+
]);
|
|
7518
|
+
return [
|
|
7519
|
+
toolId,
|
|
7520
|
+
verificationFailureProjection(
|
|
7521
|
+
activeJournal,
|
|
7522
|
+
checked.failures.filter(({ toolId }) => relevantToolIds.has(toolId)),
|
|
7523
|
+
),
|
|
7524
|
+
] as const;
|
|
7525
|
+
}),
|
|
7526
|
+
);
|
|
7527
|
+
activeJournal.revisePlan(desired, {
|
|
7528
|
+
expectedRevision: revision,
|
|
7529
|
+
forceRecompileToolIds: targetIds,
|
|
7530
|
+
decision: planDecision(
|
|
7531
|
+
deps.now(),
|
|
7532
|
+
'revised',
|
|
7533
|
+
'Repair the selected components while retaining the accepted tool contracts and dependencies.',
|
|
7534
|
+
[repairRef],
|
|
7535
|
+
[currentFailures.ref],
|
|
7536
|
+
),
|
|
7537
|
+
});
|
|
7538
|
+
for (const toolId of targetIds) {
|
|
7539
|
+
const previous = compiledByToolId.get(toolId);
|
|
7540
|
+
if (previous) revisionSourceByToolId.set(toolId, previous);
|
|
7541
|
+
const requests = componentRepairs.filter((repair) => repair.toolId === toolId);
|
|
7542
|
+
revisionGuidanceByToolId.set(
|
|
7543
|
+
toolId,
|
|
7544
|
+
`Repair this component under its existing accepted implementation plan; preserve its public contract. Consumer failures: ${JSON.stringify(requests)}. Failure evidence: ${JSON.stringify(focusedFailuresByToolId.get(toolId))}.`,
|
|
7545
|
+
);
|
|
7546
|
+
}
|
|
7547
|
+
reportProgress(opts, `repairing selected components: ${targetIds.join(', ')}`);
|
|
7548
|
+
continue;
|
|
7549
|
+
}
|
|
7007
7550
|
await repair(currentFailures);
|
|
7008
7551
|
}
|
|
7009
7552
|
continue;
|