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
|
@@ -0,0 +1,783 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join as pathJoin } from 'node:path';
|
|
3
|
+
import type { CallToolResult, Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
import {
|
|
5
|
+
AUTH_CHECKPOINT_TOOLS,
|
|
6
|
+
authExternalVerification,
|
|
7
|
+
authWorkflowPreflightFailures,
|
|
8
|
+
buildAuthCompileTools,
|
|
9
|
+
} from './auth-compile-tools.ts';
|
|
10
|
+
import type { AuthToolPlan, SharedModuleManifestEntry } from './build-plan.ts';
|
|
11
|
+
import {
|
|
12
|
+
type CompileVerificationMode,
|
|
13
|
+
advanceIncompleteSemanticVerificationRuns,
|
|
14
|
+
advanceSemanticVerificationCycle,
|
|
15
|
+
} from './compile-agent-types.ts';
|
|
16
|
+
import type { inheritedCompileProviderControl } from './compile-provider-control.ts';
|
|
17
|
+
import type { CompileStrategyKind } from './compile-strategy.ts';
|
|
18
|
+
import {
|
|
19
|
+
applyIrreversibleVerificationWaiver,
|
|
20
|
+
applyLiveVerification,
|
|
21
|
+
applyParamVerification,
|
|
22
|
+
buildCompileTools,
|
|
23
|
+
externalVerification,
|
|
24
|
+
} from './compile-tools.ts';
|
|
25
|
+
import { extractCredentials } from './credential-extract.ts';
|
|
26
|
+
import { workflowHasIrreversibleEffect } from './effects.ts';
|
|
27
|
+
import {
|
|
28
|
+
mergeSemanticParamVerification,
|
|
29
|
+
runLiveSemanticVerification,
|
|
30
|
+
semanticVerificationFailures,
|
|
31
|
+
} from './live-verifier.ts';
|
|
32
|
+
import { DEFAULT_VERIFICATION_PROVIDER } from './llm.ts';
|
|
33
|
+
import type { ProviderName } from './llm.ts';
|
|
34
|
+
import { loadJsonFile } from './load-json.ts';
|
|
35
|
+
import { createLog } from './log.ts';
|
|
36
|
+
import { rebindExistingBackendsCacheToWorkflow } from './probe-backends.ts';
|
|
37
|
+
import {
|
|
38
|
+
ProviderDeadlineError,
|
|
39
|
+
ProviderUnavailableError,
|
|
40
|
+
type RunDeadlineRef,
|
|
41
|
+
combinedDeadlineSignal,
|
|
42
|
+
providerControlError,
|
|
43
|
+
} from './provider-retry.ts';
|
|
44
|
+
import { redactSession } from './redact.ts';
|
|
45
|
+
import type { SharedCompileContext, ToolCandidate } from './tool-candidates.ts';
|
|
46
|
+
import { type SharedTriageSelection, applySharedTriageSelection } from './triage-selection.ts';
|
|
47
|
+
import { type Session, SessionSchema, type Workflow, WorkflowSchema } from './types.ts';
|
|
48
|
+
|
|
49
|
+
import { abortSignalError } from './concurrency.ts';
|
|
50
|
+
|
|
51
|
+
const log = createLog('mcp-compile');
|
|
52
|
+
|
|
53
|
+
export interface CompileToolExecutorOptions {
|
|
54
|
+
/** Agent-facing focused/redacted recording. */
|
|
55
|
+
sessionPath: string;
|
|
56
|
+
/** Absolute path to the generated tool directory where artifacts go. */
|
|
57
|
+
toolDir: string;
|
|
58
|
+
/** Data-tool cap on done() verification failures. Auth is deadline-bounded. */
|
|
59
|
+
maxVerificationCycles?: number;
|
|
60
|
+
candidate?: ToolCandidate;
|
|
61
|
+
sharedContext?: SharedCompileContext;
|
|
62
|
+
/** Absolute path to the multi-tool build plan sidecar (.build-plan.json). */
|
|
63
|
+
buildPlanPath?: string;
|
|
64
|
+
/** Shared-module build manifest for this site (verified flags). */
|
|
65
|
+
sharedModules?: SharedModuleManifestEntry[];
|
|
66
|
+
/** Master-accepted execution strategy for this focused compile. */
|
|
67
|
+
strategyKind?: CompileStrategyKind;
|
|
68
|
+
/** Present → run in AUTH mode: register the auth checkpoint toolset
|
|
69
|
+
* and run authExternalVerification in done() instead of the data-tool
|
|
70
|
+
* verification. */
|
|
71
|
+
authToolPlan?: NonNullable<AuthToolPlan>;
|
|
72
|
+
/** Site slug (required in auth mode) — used to load credentials for the live
|
|
73
|
+
* auth-test tools. */
|
|
74
|
+
site?: string;
|
|
75
|
+
/** Compile provider. The independent verifier uses the same provider family
|
|
76
|
+
* with its dedicated review model (Terra for Codex, latest Sonnet for Claude). */
|
|
77
|
+
provider?: ProviderName;
|
|
78
|
+
/** Bounded resume of an already-generated data tool. */
|
|
79
|
+
revisionMode?: boolean;
|
|
80
|
+
/** Master-only deterministic MVP boundary. */
|
|
81
|
+
verificationMode?: CompileVerificationMode;
|
|
82
|
+
/** Shared triage result from teach. Applied before compile tools and done()
|
|
83
|
+
* verification so CLI-backed compilers see irreversible effects. */
|
|
84
|
+
sharedTriageSelection?: SharedTriageSelection;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const DONE_SENTINEL = '.compile-done.json';
|
|
88
|
+
const GIVE_UP_SENTINEL = '.compile-give-up.json';
|
|
89
|
+
const VERIFICATION_STATE_SENTINEL = '.compile-verification-state.json';
|
|
90
|
+
// Keep the compile watchdog from charging live verification against the model's
|
|
91
|
+
// reasoning budget, while retaining a hard bound if the MCP server dies during
|
|
92
|
+
// done(). This matches the longest supported MCP tool call.
|
|
93
|
+
/** Auth mode only: a mid-loop checkpoint the agent reaches. The tool records
|
|
94
|
+
* the request here and the
|
|
95
|
+
* agent STOPS; the orchestrator (teach) performs the action and resumes the
|
|
96
|
+
* agent (`claude --resume`) with the result. One pending checkpoint per segment. */
|
|
97
|
+
const CHECKPOINT_SENTINEL = '.compile-checkpoint.json';
|
|
98
|
+
|
|
99
|
+
/** An irreversible workflow may skip live execution only after every
|
|
100
|
+
* deterministic artifact check passed. Keep this conjunction explicit: the
|
|
101
|
+
* waiver changes only live applicability, never deterministic correctness. */
|
|
102
|
+
export function canWaiveIrreversibleLiveVerification(
|
|
103
|
+
failures: readonly string[],
|
|
104
|
+
workflow: Workflow | undefined,
|
|
105
|
+
): workflow is Workflow {
|
|
106
|
+
return failures.length === 0 && workflow !== undefined && workflowHasIrreversibleEffect(workflow);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function compileDoneToolDescription(mode?: CompileVerificationMode): string {
|
|
110
|
+
return mode === 'master_mvp'
|
|
111
|
+
? 'Call this after completing the artifact and its offline tests. Validates deterministic artifact, schema, test, and type facts, then hands the artifact back to the master for live verification. Fix any returned deterministic failures and call done again.'
|
|
112
|
+
: 'Call this when you have successfully completed the task. Continues through independent external verification of the artifacts. If verification fails, the result will list the issues and you should fix them and call done again.';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Shared mechanical compiler tools. Native agents call these directly; CLI
|
|
116
|
+
* providers expose the same executor through MCP. No model policy lives here. */
|
|
117
|
+
export function createCompileToolExecutor(
|
|
118
|
+
opts: CompileToolExecutorOptions,
|
|
119
|
+
host: {
|
|
120
|
+
/** Already focused/redacted host evidence, retained by reference for live discovery. */
|
|
121
|
+
session?: Session;
|
|
122
|
+
signal?: AbortSignal;
|
|
123
|
+
runDeadline?: RunDeadlineRef;
|
|
124
|
+
providerControl?: ReturnType<typeof inheritedCompileProviderControl>;
|
|
125
|
+
} = {},
|
|
126
|
+
) {
|
|
127
|
+
const providerControl = host.providerControl;
|
|
128
|
+
const lifetime = new AbortController();
|
|
129
|
+
const active = combinedDeadlineSignal(
|
|
130
|
+
providerControl?.deadline ?? host.runDeadline,
|
|
131
|
+
undefined,
|
|
132
|
+
AbortSignal.any([lifetime.signal, ...(host.signal ? [host.signal] : [])]),
|
|
133
|
+
);
|
|
134
|
+
try {
|
|
135
|
+
const isAuthMode = !!opts.authToolPlan;
|
|
136
|
+
const maxVerificationCycles = opts.maxVerificationCycles ?? 5;
|
|
137
|
+
const maxIncompleteVerifierRuns = 2;
|
|
138
|
+
let incompleteVerifierRuns = 0;
|
|
139
|
+
|
|
140
|
+
// Load the recording supplied by the compile host. Teach normally passes a
|
|
141
|
+
// redacted session; direct compilation still applies ordinary in-memory
|
|
142
|
+
// redaction before exposing request evidence through compile tools.
|
|
143
|
+
let session: Session =
|
|
144
|
+
host.session ??
|
|
145
|
+
loadJsonFile(
|
|
146
|
+
opts.sessionPath,
|
|
147
|
+
SessionSchema,
|
|
148
|
+
{
|
|
149
|
+
notFound: '→ run `imprint record <site>` to create one.',
|
|
150
|
+
notJson: `→ if it's a partial .jsonl, run \`imprint assemble ${opts.sessionPath}\` first.`,
|
|
151
|
+
badSchema: '→ check the file came from `imprint record`.',
|
|
152
|
+
},
|
|
153
|
+
'session',
|
|
154
|
+
);
|
|
155
|
+
if (!host.session && !JSON.stringify(session).includes('[REDACTED:')) {
|
|
156
|
+
session = redactSession(session, {
|
|
157
|
+
replacements: extractCredentials(session).replacements,
|
|
158
|
+
}).session;
|
|
159
|
+
}
|
|
160
|
+
if (opts.sharedTriageSelection) {
|
|
161
|
+
session = applySharedTriageSelection(session, opts.sharedTriageSelection, {
|
|
162
|
+
candidate: opts.candidate,
|
|
163
|
+
sharedContext: opts.sharedContext,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Build the toolset. Auth mode swaps the data tools (parser/test-oriented)
|
|
168
|
+
// for the auth toolset, which lives in auth-compile-tools.ts so the in-process
|
|
169
|
+
// loop and this MCP path stay byte-for-byte identical.
|
|
170
|
+
let compileTools: ReturnType<typeof buildCompileTools>;
|
|
171
|
+
if (isAuthMode) {
|
|
172
|
+
const site = opts.site ?? session.site;
|
|
173
|
+
const teachCredentials = { site, values: {} };
|
|
174
|
+
compileTools = buildAuthCompileTools(
|
|
175
|
+
session,
|
|
176
|
+
opts.toolDir,
|
|
177
|
+
opts.sessionPath,
|
|
178
|
+
teachCredentials,
|
|
179
|
+
);
|
|
180
|
+
} else {
|
|
181
|
+
// When a build plan is present, buildCompileTools also exposes read_build_plan.
|
|
182
|
+
compileTools = buildCompileTools(session, opts.toolDir, opts.sessionPath, {
|
|
183
|
+
candidate: opts.candidate,
|
|
184
|
+
sharedContext: opts.sharedContext,
|
|
185
|
+
buildPlanPath: opts.buildPlanPath,
|
|
186
|
+
sharedModules: opts.sharedModules,
|
|
187
|
+
strategyKind: opts.strategyKind,
|
|
188
|
+
revisionMode: opts.revisionMode,
|
|
189
|
+
signal: active.signal,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// The custom done/give_up tools live alongside in MCP space.
|
|
194
|
+
const doneTool: Tool = {
|
|
195
|
+
name: 'done',
|
|
196
|
+
description: compileDoneToolDescription(opts.verificationMode),
|
|
197
|
+
inputSchema: {
|
|
198
|
+
type: 'object',
|
|
199
|
+
properties: {
|
|
200
|
+
summary: { type: 'string', description: 'Brief summary of what was accomplished' },
|
|
201
|
+
},
|
|
202
|
+
required: ['summary'],
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
const giveUpTool: Tool = {
|
|
206
|
+
name: 'give_up',
|
|
207
|
+
description:
|
|
208
|
+
'Use for a categorical impossibility or an exact accepted-plan contradiction that only the master can revise.',
|
|
209
|
+
inputSchema: {
|
|
210
|
+
type: 'object',
|
|
211
|
+
properties: {
|
|
212
|
+
reason: { type: 'string', description: 'Why you cannot complete the task' },
|
|
213
|
+
what_was_tried: {
|
|
214
|
+
type: 'string',
|
|
215
|
+
description: 'Summary of approaches you tried before giving up',
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
required: ['reason', 'what_was_tried'],
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// Auth mode only: checkpoint tools. Each records its request to the checkpoint
|
|
223
|
+
// sentinel and instructs the agent to STOP; the orchestrator performs the
|
|
224
|
+
// action live (it owns the persistent browser session + the user TUI) and
|
|
225
|
+
// resumes the agent with the result. The agent never runs a live login itself.
|
|
226
|
+
const checkpointTools: Tool[] = isAuthMode
|
|
227
|
+
? AUTH_CHECKPOINT_TOOLS.map(({ name, description, input_schema }) => ({
|
|
228
|
+
name,
|
|
229
|
+
description,
|
|
230
|
+
inputSchema: input_schema as Tool['inputSchema'],
|
|
231
|
+
}))
|
|
232
|
+
: [];
|
|
233
|
+
|
|
234
|
+
let semanticVerificationCycles = 0;
|
|
235
|
+
/** One pending checkpoint per segment — refuse a second so the orchestrator
|
|
236
|
+
* acts on exactly one request. Cleared implicitly: each segment is a fresh
|
|
237
|
+
* process with the sentinel removed before spawn. */
|
|
238
|
+
let checkpointWritten = false;
|
|
239
|
+
|
|
240
|
+
const tools: Tool[] = [
|
|
241
|
+
...compileTools.map((t) => ({
|
|
242
|
+
name: t.name,
|
|
243
|
+
description: t.description,
|
|
244
|
+
inputSchema: t.input_schema as Tool['inputSchema'],
|
|
245
|
+
})),
|
|
246
|
+
...checkpointTools,
|
|
247
|
+
doneTool,
|
|
248
|
+
giveUpTool,
|
|
249
|
+
];
|
|
250
|
+
const pendingCalls = new Set<Promise<void>>();
|
|
251
|
+
const callTool = async (
|
|
252
|
+
name: string,
|
|
253
|
+
args: Record<string, unknown> = {},
|
|
254
|
+
signal?: AbortSignal,
|
|
255
|
+
): Promise<CallToolResult> => {
|
|
256
|
+
const requestSignal = AbortSignal.any([
|
|
257
|
+
active.signal ?? lifetime.signal,
|
|
258
|
+
...(signal ? [signal] : []),
|
|
259
|
+
]);
|
|
260
|
+
let settle!: () => void;
|
|
261
|
+
const pending = new Promise<void>((resolve) => {
|
|
262
|
+
settle = resolve;
|
|
263
|
+
});
|
|
264
|
+
pendingCalls.add(pending);
|
|
265
|
+
try {
|
|
266
|
+
if (requestSignal.aborted) throw abortSignalError(requestSignal);
|
|
267
|
+
|
|
268
|
+
// Custom done — runs verification inline.
|
|
269
|
+
if (name === 'done') {
|
|
270
|
+
const doneArgs = args as { summary?: string };
|
|
271
|
+
const summary = doneArgs.summary ?? 'Task completed';
|
|
272
|
+
log(`done() called: ${summary}`);
|
|
273
|
+
|
|
274
|
+
// Auth mode: lightweight structural verification (the agent already proved
|
|
275
|
+
// the workflow works live via run_verification). No param/live stamps.
|
|
276
|
+
if (isAuthMode) {
|
|
277
|
+
const failures = authExternalVerification(
|
|
278
|
+
opts.toolDir,
|
|
279
|
+
(opts.authToolPlan?.captures ?? []).map((c) => ({
|
|
280
|
+
name: c.name,
|
|
281
|
+
usedAs: c.usedAs,
|
|
282
|
+
})),
|
|
283
|
+
{ requireLiveAttempt: true },
|
|
284
|
+
);
|
|
285
|
+
if (failures.length === 0) {
|
|
286
|
+
const sentinel = pathJoin(opts.toolDir, DONE_SENTINEL);
|
|
287
|
+
writeFileSync(
|
|
288
|
+
sentinel,
|
|
289
|
+
JSON.stringify(
|
|
290
|
+
{
|
|
291
|
+
summary,
|
|
292
|
+
verification: 'mechanical_passed',
|
|
293
|
+
warnings: [],
|
|
294
|
+
timestamp: Date.now(),
|
|
295
|
+
},
|
|
296
|
+
null,
|
|
297
|
+
2,
|
|
298
|
+
),
|
|
299
|
+
'utf8',
|
|
300
|
+
);
|
|
301
|
+
log(`auth verification passed; wrote ${sentinel}`);
|
|
302
|
+
return {
|
|
303
|
+
content: [
|
|
304
|
+
{
|
|
305
|
+
type: 'text',
|
|
306
|
+
text: 'DONE — verification passed. Do not call any more tools.',
|
|
307
|
+
},
|
|
308
|
+
],
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
semanticVerificationCycles++;
|
|
313
|
+
log(`auth verification failed (cycle ${semanticVerificationCycles})`);
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
isError: true,
|
|
317
|
+
content: [
|
|
318
|
+
{
|
|
319
|
+
type: 'text',
|
|
320
|
+
text: `You called done but verification failed (cycle ${semanticVerificationCycles}):
|
|
321
|
+
|
|
322
|
+
${failures.map((f) => `- ${f}`).join('\n')}
|
|
323
|
+
|
|
324
|
+
Fix the issues in workflow.json, re-test with run_verification, and call done again when fixed.`,
|
|
325
|
+
},
|
|
326
|
+
],
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
let semanticReviewCompleted = false;
|
|
331
|
+
let semanticReviewAttempted = false;
|
|
332
|
+
const { failures, warnings, paramVerification, integrationEvidence } =
|
|
333
|
+
await externalVerification(opts.toolDir, session, opts.sessionPath, {
|
|
334
|
+
signal: requestSignal,
|
|
335
|
+
expectedToolName: opts.candidate?.toolName,
|
|
336
|
+
candidateRequestSeqs: opts.candidate?.requestSeqs,
|
|
337
|
+
// Include bootstrap and shared login dependencies in provenance checks.
|
|
338
|
+
dependencyRequestSeqs: [
|
|
339
|
+
...(opts.candidate?.dependencySeqs ?? []),
|
|
340
|
+
...(opts.sharedContext?.loginRequestSeqs ?? []),
|
|
341
|
+
],
|
|
342
|
+
credentialNames: opts.sharedContext?.credentialNames,
|
|
343
|
+
strategyKind: opts.strategyKind,
|
|
344
|
+
deferLiveIntegrationToSemanticAgent: true,
|
|
345
|
+
expectedPublicParameters:
|
|
346
|
+
opts.verificationMode === 'master_mvp' ? opts.candidate?.likelyParams : undefined,
|
|
347
|
+
});
|
|
348
|
+
if (warnings.length > 0) {
|
|
349
|
+
log(`verification warnings (non-blocking):\n${warnings.join('\n')}`);
|
|
350
|
+
}
|
|
351
|
+
let workflow: ReturnType<typeof WorkflowSchema.parse> | undefined;
|
|
352
|
+
if (failures.length === 0) {
|
|
353
|
+
workflow = WorkflowSchema.parse(
|
|
354
|
+
JSON.parse(readFileSync(pathJoin(opts.toolDir, 'workflow.json'), 'utf8')),
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
if (
|
|
358
|
+
failures.length === 0 &&
|
|
359
|
+
workflow &&
|
|
360
|
+
opts.strategyKind === 'playbook_fallback' &&
|
|
361
|
+
opts.verificationMode === 'master_mvp'
|
|
362
|
+
) {
|
|
363
|
+
const sentinel = pathJoin(opts.toolDir, DONE_SENTINEL);
|
|
364
|
+
writeFileSync(
|
|
365
|
+
sentinel,
|
|
366
|
+
JSON.stringify(
|
|
367
|
+
{
|
|
368
|
+
summary,
|
|
369
|
+
verification: 'mechanical_passed',
|
|
370
|
+
verificationMode: opts.verificationMode ?? 'full',
|
|
371
|
+
liveVerified: false,
|
|
372
|
+
liveVerificationOwner: 'master',
|
|
373
|
+
semanticVerification: { status: 'not_run' },
|
|
374
|
+
warnings,
|
|
375
|
+
timestamp: Date.now(),
|
|
376
|
+
},
|
|
377
|
+
null,
|
|
378
|
+
2,
|
|
379
|
+
),
|
|
380
|
+
'utf8',
|
|
381
|
+
);
|
|
382
|
+
log(`browser artifact contract passed; wrote ${sentinel}`);
|
|
383
|
+
return {
|
|
384
|
+
content: [
|
|
385
|
+
{
|
|
386
|
+
type: 'text',
|
|
387
|
+
text: 'DONE — browser artifact contract passed. The master owns live playbook verification. Do not call any more tools.',
|
|
388
|
+
},
|
|
389
|
+
],
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
if (canWaiveIrreversibleLiveVerification(failures, workflow)) {
|
|
393
|
+
const allWarnings = [
|
|
394
|
+
...warnings,
|
|
395
|
+
...applyIrreversibleVerificationWaiver(opts.toolDir, workflow),
|
|
396
|
+
];
|
|
397
|
+
const sentinel = pathJoin(opts.toolDir, DONE_SENTINEL);
|
|
398
|
+
writeFileSync(
|
|
399
|
+
sentinel,
|
|
400
|
+
JSON.stringify(
|
|
401
|
+
{
|
|
402
|
+
summary,
|
|
403
|
+
verification: 'not_applicable',
|
|
404
|
+
verificationMode: opts.verificationMode ?? 'full',
|
|
405
|
+
liveVerified: false,
|
|
406
|
+
safetyWaiver: 'irreversible',
|
|
407
|
+
semanticVerification: { status: 'not_applicable' },
|
|
408
|
+
warnings: allWarnings,
|
|
409
|
+
timestamp: Date.now(),
|
|
410
|
+
},
|
|
411
|
+
null,
|
|
412
|
+
2,
|
|
413
|
+
),
|
|
414
|
+
'utf8',
|
|
415
|
+
);
|
|
416
|
+
return {
|
|
417
|
+
content: [
|
|
418
|
+
{
|
|
419
|
+
type: 'text',
|
|
420
|
+
text: 'DONE_LIVE_NA — deterministic checks passed; live verification is not applicable because the workflow is irreversible.',
|
|
421
|
+
},
|
|
422
|
+
],
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
if (failures.length === 0 && opts.verificationMode === 'master_mvp') {
|
|
426
|
+
const sentinel = pathJoin(opts.toolDir, DONE_SENTINEL);
|
|
427
|
+
writeFileSync(
|
|
428
|
+
sentinel,
|
|
429
|
+
JSON.stringify(
|
|
430
|
+
{
|
|
431
|
+
summary,
|
|
432
|
+
verification: 'mechanical_passed',
|
|
433
|
+
verificationMode: 'master_mvp',
|
|
434
|
+
liveVerified: false,
|
|
435
|
+
semanticVerification: { status: 'not_run' },
|
|
436
|
+
warnings,
|
|
437
|
+
timestamp: Date.now(),
|
|
438
|
+
},
|
|
439
|
+
null,
|
|
440
|
+
2,
|
|
441
|
+
),
|
|
442
|
+
'utf8',
|
|
443
|
+
);
|
|
444
|
+
log(`minimum viable artifact verification passed; wrote ${sentinel}`);
|
|
445
|
+
return {
|
|
446
|
+
content: [
|
|
447
|
+
{
|
|
448
|
+
type: 'text',
|
|
449
|
+
text: 'DONE — deterministic artifact checks passed. Live semantic verification is deferred to the master. Do not call any more tools.',
|
|
450
|
+
},
|
|
451
|
+
],
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
if (failures.length === 0) {
|
|
455
|
+
if (!opts.provider) {
|
|
456
|
+
failures.push(
|
|
457
|
+
'compile provider was not supplied to the independent semantic verifier',
|
|
458
|
+
);
|
|
459
|
+
} else {
|
|
460
|
+
semanticReviewAttempted = true;
|
|
461
|
+
const verifierDeadlineMs =
|
|
462
|
+
providerControl?.deadlineMs ?? host.runDeadline?.deadlineMs;
|
|
463
|
+
if (
|
|
464
|
+
providerControl &&
|
|
465
|
+
verifierDeadlineMs !== undefined &&
|
|
466
|
+
Date.now() >= verifierDeadlineMs
|
|
467
|
+
) {
|
|
468
|
+
const error = new ProviderDeadlineError(verifierDeadlineMs);
|
|
469
|
+
providerControl.report(error);
|
|
470
|
+
throw error;
|
|
471
|
+
}
|
|
472
|
+
let semantic: Awaited<ReturnType<typeof runLiveSemanticVerification>>;
|
|
473
|
+
try {
|
|
474
|
+
semantic = await runLiveSemanticVerification({
|
|
475
|
+
provider: DEFAULT_VERIFICATION_PROVIDER,
|
|
476
|
+
toolDir: opts.toolDir,
|
|
477
|
+
evidence: integrationEvidence,
|
|
478
|
+
deadlineMs: verifierDeadlineMs,
|
|
479
|
+
runDeadline: providerControl?.deadline ?? host.runDeadline,
|
|
480
|
+
signal: requestSignal,
|
|
481
|
+
});
|
|
482
|
+
} catch (error) {
|
|
483
|
+
const control = providerControlError(error);
|
|
484
|
+
if (
|
|
485
|
+
providerControl &&
|
|
486
|
+
(control instanceof ProviderDeadlineError ||
|
|
487
|
+
control instanceof ProviderUnavailableError)
|
|
488
|
+
)
|
|
489
|
+
providerControl.report(control);
|
|
490
|
+
throw error;
|
|
491
|
+
}
|
|
492
|
+
semanticReviewCompleted = semantic.completedReview;
|
|
493
|
+
const semanticFailures = semanticVerificationFailures(semantic.report);
|
|
494
|
+
failures.push(...semanticFailures);
|
|
495
|
+
if (semantic.report.status === 'approved_with_gaps') {
|
|
496
|
+
warnings.push(
|
|
497
|
+
`independent semantic verification approved with gaps: ${semantic.report.gaps.join('; ')}`,
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
if (semanticFailures.length === 0) {
|
|
501
|
+
const semanticParams = mergeSemanticParamVerification(
|
|
502
|
+
paramVerification,
|
|
503
|
+
semantic.report,
|
|
504
|
+
);
|
|
505
|
+
applyLiveVerification(opts.toolDir, undefined);
|
|
506
|
+
const paramWarnings = applyParamVerification(opts.toolDir, semanticParams);
|
|
507
|
+
rebindExistingBackendsCacheToWorkflow(opts.toolDir);
|
|
508
|
+
if (paramWarnings.length > 0) {
|
|
509
|
+
log(`parameter verification:\n${paramWarnings.join('\n')}`);
|
|
510
|
+
}
|
|
511
|
+
const allWarnings = [...warnings, ...paramWarnings];
|
|
512
|
+
const sentinel = pathJoin(opts.toolDir, DONE_SENTINEL);
|
|
513
|
+
writeFileSync(
|
|
514
|
+
sentinel,
|
|
515
|
+
JSON.stringify(
|
|
516
|
+
{
|
|
517
|
+
summary,
|
|
518
|
+
verification: 'mechanical_passed',
|
|
519
|
+
verificationMode: 'full',
|
|
520
|
+
liveVerified: true,
|
|
521
|
+
semanticVerification: {
|
|
522
|
+
status: semantic.report.status,
|
|
523
|
+
completed: semantic.completedReview,
|
|
524
|
+
provider: semantic.provider,
|
|
525
|
+
model: semantic.model,
|
|
526
|
+
attempts: semantic.attempts,
|
|
527
|
+
evidenceArtifact: semantic.report.evidenceArtifact,
|
|
528
|
+
logArtifact: semantic.report.logArtifact,
|
|
529
|
+
},
|
|
530
|
+
warnings: allWarnings,
|
|
531
|
+
timestamp: Date.now(),
|
|
532
|
+
},
|
|
533
|
+
null,
|
|
534
|
+
2,
|
|
535
|
+
),
|
|
536
|
+
'utf8',
|
|
537
|
+
);
|
|
538
|
+
log(`verification passed; wrote ${sentinel}`);
|
|
539
|
+
return {
|
|
540
|
+
content: [
|
|
541
|
+
{
|
|
542
|
+
type: 'text',
|
|
543
|
+
text: `DONE — deterministic checks and independent live semantic verification passed. Evidence: ${semantic.report.evidenceArtifact ?? '.live-verification-evidence.json'}. Verifier log: ${semantic.report.logArtifact ?? '.live-verifier-log.jsonl'}. Do not call any more tools.`,
|
|
544
|
+
},
|
|
545
|
+
],
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
semanticVerificationCycles = advanceSemanticVerificationCycle(
|
|
551
|
+
semanticVerificationCycles,
|
|
552
|
+
semanticReviewCompleted,
|
|
553
|
+
);
|
|
554
|
+
incompleteVerifierRuns = advanceIncompleteSemanticVerificationRuns(
|
|
555
|
+
incompleteVerifierRuns,
|
|
556
|
+
semanticReviewAttempted,
|
|
557
|
+
semanticReviewCompleted,
|
|
558
|
+
);
|
|
559
|
+
if (incompleteVerifierRuns >= maxIncompleteVerifierRuns) {
|
|
560
|
+
const sentinel = pathJoin(opts.toolDir, DONE_SENTINEL);
|
|
561
|
+
writeFileSync(
|
|
562
|
+
sentinel,
|
|
563
|
+
JSON.stringify(
|
|
564
|
+
{
|
|
565
|
+
summary,
|
|
566
|
+
verification: 'failed',
|
|
567
|
+
incompleteVerifierRuns,
|
|
568
|
+
failures,
|
|
569
|
+
warnings,
|
|
570
|
+
timestamp: Date.now(),
|
|
571
|
+
},
|
|
572
|
+
null,
|
|
573
|
+
2,
|
|
574
|
+
),
|
|
575
|
+
'utf8',
|
|
576
|
+
);
|
|
577
|
+
return {
|
|
578
|
+
isError: true,
|
|
579
|
+
content: [
|
|
580
|
+
{
|
|
581
|
+
type: 'text',
|
|
582
|
+
text: `Independent semantic verifier failed to complete after ${maxIncompleteVerifierRuns} bounded runs. Final failures:\n${failures.map((failure) => `- ${failure}`).join('\n')}`,
|
|
583
|
+
},
|
|
584
|
+
],
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
const failurePhase = semanticReviewCompleted
|
|
588
|
+
? `semantic verification failed (cycle ${semanticVerificationCycles}/${maxVerificationCycles})`
|
|
589
|
+
: semanticReviewAttempted
|
|
590
|
+
? 'semantic verifier did not complete a review (semantic cycle unchanged)'
|
|
591
|
+
: 'deterministic verification failed (semantic cycle unchanged)';
|
|
592
|
+
log(failurePhase);
|
|
593
|
+
if (semanticVerificationCycles >= maxVerificationCycles) {
|
|
594
|
+
const sentinel = pathJoin(opts.toolDir, DONE_SENTINEL);
|
|
595
|
+
writeFileSync(
|
|
596
|
+
sentinel,
|
|
597
|
+
JSON.stringify(
|
|
598
|
+
{
|
|
599
|
+
summary,
|
|
600
|
+
verification: 'failed',
|
|
601
|
+
cycles: semanticVerificationCycles,
|
|
602
|
+
failures,
|
|
603
|
+
warnings,
|
|
604
|
+
timestamp: Date.now(),
|
|
605
|
+
},
|
|
606
|
+
null,
|
|
607
|
+
2,
|
|
608
|
+
),
|
|
609
|
+
'utf8',
|
|
610
|
+
);
|
|
611
|
+
return {
|
|
612
|
+
isError: true,
|
|
613
|
+
content: [
|
|
614
|
+
{
|
|
615
|
+
type: 'text',
|
|
616
|
+
text: `Semantic verification failed after ${maxVerificationCycles} cycles. Giving up. Final failures:\n${failures.map((f) => `- ${f}`).join('\n')}`,
|
|
617
|
+
},
|
|
618
|
+
],
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const repairFiles =
|
|
623
|
+
opts.strategyKind === 'playbook_fallback'
|
|
624
|
+
? 'workflow.json and playbook.yaml'
|
|
625
|
+
: 'workflow.json, parser.ts, and parser.test.ts';
|
|
626
|
+
const continuationMessage = `You called done but ${failurePhase}:
|
|
627
|
+
|
|
628
|
+
${failures.map((f) => `- ${f}`).join('\n')}
|
|
629
|
+
|
|
630
|
+
Resume your work. Read the files you wrote (${repairFiles}), fix the issues, re-run the applicable checks, and call done again when fixed.`;
|
|
631
|
+
return {
|
|
632
|
+
isError: true,
|
|
633
|
+
content: [
|
|
634
|
+
{
|
|
635
|
+
type: 'text',
|
|
636
|
+
text: continuationMessage,
|
|
637
|
+
},
|
|
638
|
+
],
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// Auth-mode checkpoint tools — record the request and end the segment. The
|
|
643
|
+
// orchestrator performs the action live and resumes the agent with the result.
|
|
644
|
+
if (
|
|
645
|
+
isAuthMode &&
|
|
646
|
+
(name === 'run_verification' ||
|
|
647
|
+
name === 'inspect_verification_page' ||
|
|
648
|
+
name === 'interact_verification_browser' ||
|
|
649
|
+
name === 'prompt_user' ||
|
|
650
|
+
name === 'wait_for_cooldown')
|
|
651
|
+
) {
|
|
652
|
+
if (name === 'run_verification') {
|
|
653
|
+
const failures = authWorkflowPreflightFailures(
|
|
654
|
+
opts.toolDir,
|
|
655
|
+
session,
|
|
656
|
+
opts.authToolPlan?.credentialNames,
|
|
657
|
+
);
|
|
658
|
+
if (failures.length > 0) {
|
|
659
|
+
return {
|
|
660
|
+
isError: true,
|
|
661
|
+
content: [
|
|
662
|
+
{
|
|
663
|
+
type: 'text',
|
|
664
|
+
text: `run_verification blocked by auth workflow preflight. Fix workflow.json before requesting live verification:\n${failures
|
|
665
|
+
.map((failure) => `- ${failure}`)
|
|
666
|
+
.join('\n')}`,
|
|
667
|
+
},
|
|
668
|
+
],
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
if (checkpointWritten) {
|
|
673
|
+
return {
|
|
674
|
+
isError: true,
|
|
675
|
+
content: [
|
|
676
|
+
{
|
|
677
|
+
type: 'text',
|
|
678
|
+
text: 'A checkpoint is already pending this turn. STOP now — do not call another tool; the orchestrator will act and resume you.',
|
|
679
|
+
},
|
|
680
|
+
],
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
const checkpoint: Record<string, unknown> = {
|
|
684
|
+
kind: name,
|
|
685
|
+
...args,
|
|
686
|
+
timestamp: Date.now(),
|
|
687
|
+
};
|
|
688
|
+
const sentinel = pathJoin(opts.toolDir, CHECKPOINT_SENTINEL);
|
|
689
|
+
writeFileSync(sentinel, JSON.stringify(checkpoint, null, 2), 'utf8');
|
|
690
|
+
checkpointWritten = true;
|
|
691
|
+
log(`checkpoint(${name}) recorded; wrote ${sentinel}`);
|
|
692
|
+
return {
|
|
693
|
+
content: [
|
|
694
|
+
{
|
|
695
|
+
type: 'text',
|
|
696
|
+
text: `CHECKPOINT_RECORDED (${name}) — STOP now and reply briefly that you are waiting. The orchestrator will perform this and resume you with the result as a new message. Do not call any more tools.`,
|
|
697
|
+
},
|
|
698
|
+
],
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Custom give_up — writes sentinel and exits.
|
|
703
|
+
if (name === 'give_up') {
|
|
704
|
+
const reason = (args as { reason?: string }).reason ?? 'unknown';
|
|
705
|
+
const whatWasTried = (args as { what_was_tried?: string }).what_was_tried ?? '';
|
|
706
|
+
log(`give_up() called: ${reason}`);
|
|
707
|
+
const sentinel = pathJoin(opts.toolDir, GIVE_UP_SENTINEL);
|
|
708
|
+
writeFileSync(
|
|
709
|
+
sentinel,
|
|
710
|
+
JSON.stringify(
|
|
711
|
+
{ reason, what_was_tried: whatWasTried, timestamp: Date.now() },
|
|
712
|
+
null,
|
|
713
|
+
2,
|
|
714
|
+
),
|
|
715
|
+
'utf8',
|
|
716
|
+
);
|
|
717
|
+
return {
|
|
718
|
+
content: [
|
|
719
|
+
{
|
|
720
|
+
type: 'text',
|
|
721
|
+
text: 'GIVE_UP_RECORDED — the orchestrator will exit shortly. Do not call any more tools.',
|
|
722
|
+
},
|
|
723
|
+
],
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Standard read/write tools — delegate to the shared handlers.
|
|
728
|
+
const tool = compileTools.find((t) => t.name === name);
|
|
729
|
+
if (!tool) {
|
|
730
|
+
return {
|
|
731
|
+
isError: true,
|
|
732
|
+
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
let result: { result: string; isError?: boolean };
|
|
737
|
+
try {
|
|
738
|
+
result = await tool.handler(args, { signal: requestSignal });
|
|
739
|
+
} catch (err) {
|
|
740
|
+
if (requestSignal.aborted) throw abortSignalError(requestSignal);
|
|
741
|
+
const control = providerControlError(err);
|
|
742
|
+
if (control) throw control;
|
|
743
|
+
result = {
|
|
744
|
+
result: err instanceof Error ? err.message : String(err),
|
|
745
|
+
isError: true,
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
return {
|
|
750
|
+
content: [{ type: 'text', text: result.result }],
|
|
751
|
+
isError: result.isError ?? false,
|
|
752
|
+
};
|
|
753
|
+
} finally {
|
|
754
|
+
pendingCalls.delete(pending);
|
|
755
|
+
settle();
|
|
756
|
+
}
|
|
757
|
+
};
|
|
758
|
+
return {
|
|
759
|
+
tools,
|
|
760
|
+
instructions: isAuthMode
|
|
761
|
+
? 'Compile the recorded login into an auth action program in workflow.json. Define the actions, evidence, state carry, retries, and success criteria from the recording; run those actions live with run_verification, then call done after a declared success action passes.'
|
|
762
|
+
: 'These tools let you reverse-engineer the captured session into workflow.json + parser.ts + parser.test.ts. Read the recording, write the artifacts, run tests, and call done() when verified. The done tool runs external verification and will tell you what to fix if anything is wrong.',
|
|
763
|
+
callTool,
|
|
764
|
+
async close() {
|
|
765
|
+
lifetime.abort(new DOMException('Compile executor ended', 'AbortError'));
|
|
766
|
+
await Promise.allSettled([...pendingCalls]);
|
|
767
|
+
active.dispose();
|
|
768
|
+
},
|
|
769
|
+
};
|
|
770
|
+
} catch (error) {
|
|
771
|
+
lifetime.abort(error);
|
|
772
|
+
active.dispose();
|
|
773
|
+
throw error;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/** Sentinel file names exposed for the orchestrator to poll. */
|
|
778
|
+
export const COMPILE_SENTINELS = {
|
|
779
|
+
done: DONE_SENTINEL,
|
|
780
|
+
giveUp: GIVE_UP_SENTINEL,
|
|
781
|
+
checkpoint: CHECKPOINT_SENTINEL,
|
|
782
|
+
verificationState: VERIFICATION_STATE_SENTINEL,
|
|
783
|
+
} as const;
|