imprint-mcp 0.6.0 → 0.6.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 +2 -0
- package/package.json +4 -2
- package/prompts/audit-agent.md +4 -2
- package/prompts/auth-compile-agent.md +149 -158
- package/prompts/build-planning.md +14 -9
- package/prompts/compile-agent.md +46 -23
- package/prompts/live-verifier-agent.md +101 -0
- package/prompts/playbook-compilation.md +8 -1
- package/prompts/tool-candidate-detection.md +9 -29
- package/src/cli.ts +83 -1
- package/src/imprint/audit.ts +219 -20
- package/src/imprint/auth-compile-agent.ts +215 -155
- package/src/imprint/auth-compile-tools.ts +300 -118
- package/src/imprint/auth-verifier.ts +167 -180
- package/src/imprint/backend-cache.ts +18 -11
- package/src/imprint/backend-ladder.ts +225 -177
- package/src/imprint/build-plan.ts +3 -8
- package/src/imprint/cdp-browser-fetch.ts +930 -83
- package/src/imprint/claude-cli-compile.ts +46 -20
- package/src/imprint/codex-cli-compile.ts +436 -139
- package/src/imprint/compile-agent-types.ts +15 -10
- package/src/imprint/compile-agent.ts +88 -28
- package/src/imprint/compile-tools.ts +228 -39
- package/src/imprint/compile-verification.ts +187 -0
- package/src/imprint/compile.ts +71 -2
- package/src/imprint/credential-store.ts +57 -44
- package/src/imprint/emit.ts +3 -1
- package/src/imprint/freeform-redact.ts +27 -6
- package/src/imprint/live-verifier.ts +1665 -0
- package/src/imprint/llm.ts +22 -3
- package/src/imprint/login.ts +107 -25
- package/src/imprint/mcp-compile-server.ts +447 -114
- package/src/imprint/mcp-live-verifier-server.ts +359 -0
- package/src/imprint/mcp-server.ts +116 -35
- package/src/imprint/probe-backends.ts +88 -7
- package/src/imprint/recording-request.ts +36 -0
- package/src/imprint/request-capture.ts +40 -17
- package/src/imprint/runtime.ts +490 -337
- package/src/imprint/teach-state.ts +36 -2
- package/src/imprint/teach.ts +302 -146
- package/src/imprint/tool-candidates.ts +8 -10
- package/src/imprint/tool-loader.ts +12 -4
- package/src/imprint/types.ts +137 -68
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
* loss.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import {
|
|
17
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
18
|
+
import { existsSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
18
19
|
import { join as pathJoin } from 'node:path';
|
|
19
20
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
20
21
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
@@ -24,7 +25,11 @@ import {
|
|
|
24
25
|
ListToolsRequestSchema,
|
|
25
26
|
type Tool,
|
|
26
27
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
27
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
authExternalVerification,
|
|
30
|
+
authWorkflowPreflightFailures,
|
|
31
|
+
buildAuthCompileTools,
|
|
32
|
+
} from './auth-compile-tools.ts';
|
|
28
33
|
import {
|
|
29
34
|
type AuthToolPlan,
|
|
30
35
|
type SharedModuleManifestEntry,
|
|
@@ -36,8 +41,15 @@ import {
|
|
|
36
41
|
buildCompileTools,
|
|
37
42
|
externalVerification,
|
|
38
43
|
} from './compile-tools.ts';
|
|
44
|
+
import {
|
|
45
|
+
mergeSemanticParamVerification,
|
|
46
|
+
runLiveSemanticVerification,
|
|
47
|
+
semanticVerificationFailures,
|
|
48
|
+
} from './live-verifier.ts';
|
|
49
|
+
import type { ProviderName } from './llm.ts';
|
|
39
50
|
import { loadJsonFile } from './load-json.ts';
|
|
40
51
|
import { createLog } from './log.ts';
|
|
52
|
+
import { rebindExistingBackendsCacheToWorkflow } from './probe-backends.ts';
|
|
41
53
|
import { redactSession } from './redact.ts';
|
|
42
54
|
import { loadCredentialStore } from './runtime.ts';
|
|
43
55
|
import type { SharedCompileContext, ToolCandidate } from './tool-candidates.ts';
|
|
@@ -50,8 +62,7 @@ interface RunCompileMcpServerOptions {
|
|
|
50
62
|
sessionPath: string;
|
|
51
63
|
/** Absolute path to the generated tool directory where artifacts go. */
|
|
52
64
|
toolDir: string;
|
|
53
|
-
/**
|
|
54
|
-
* Mirrors compile-agent.ts MAX_VERIFICATION_CYCLES. */
|
|
65
|
+
/** Data-tool cap on done() verification failures. Auth is deadline-bounded. */
|
|
55
66
|
maxVerificationCycles?: number;
|
|
56
67
|
candidate?: ToolCandidate;
|
|
57
68
|
sharedContext?: SharedCompileContext;
|
|
@@ -59,26 +70,162 @@ interface RunCompileMcpServerOptions {
|
|
|
59
70
|
buildPlanPath?: string;
|
|
60
71
|
/** Shared-module build manifest for this site (verified flags). */
|
|
61
72
|
sharedModules?: SharedModuleManifestEntry[];
|
|
62
|
-
/** Present → run in AUTH mode: register the auth toolset
|
|
73
|
+
/** Present → run in AUTH mode: register the auth checkpoint toolset
|
|
63
74
|
* and run authExternalVerification in done() instead of the data-tool
|
|
64
75
|
* verification. */
|
|
65
76
|
authToolPlan?: NonNullable<AuthToolPlan>;
|
|
66
77
|
/** Site slug (required in auth mode) — used to load credentials for the live
|
|
67
78
|
* auth-test tools. */
|
|
68
79
|
site?: string;
|
|
80
|
+
/** Compile provider. The independent verifier uses the same provider family
|
|
81
|
+
* with its dedicated review model (Terra for Codex, latest Sonnet for Claude). */
|
|
82
|
+
provider?: ProviderName;
|
|
83
|
+
/** Bounded resume of an already-generated data tool. */
|
|
84
|
+
revisionMode?: boolean;
|
|
69
85
|
}
|
|
70
86
|
|
|
71
87
|
const DONE_SENTINEL = '.compile-done.json';
|
|
72
88
|
const GIVE_UP_SENTINEL = '.compile-give-up.json';
|
|
73
|
-
|
|
74
|
-
|
|
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
|
+
const MAX_VERIFICATION_WALL_TIME_MS = 30 * 60_000;
|
|
94
|
+
/** Auth mode only: a mid-loop checkpoint the agent reaches. The tool records
|
|
95
|
+
* the request here and the
|
|
75
96
|
* agent STOPS; the orchestrator (teach) performs the action and resumes the
|
|
76
97
|
* agent (`claude --resume`) with the result. One pending checkpoint per segment. */
|
|
77
98
|
const CHECKPOINT_SENTINEL = '.compile-checkpoint.json';
|
|
78
99
|
|
|
100
|
+
const COMPILE_ARTIFACT_FILES = [
|
|
101
|
+
'workflow.json',
|
|
102
|
+
'parser.ts',
|
|
103
|
+
'parser.test.ts',
|
|
104
|
+
'integration.test.ts',
|
|
105
|
+
'request-transform.ts',
|
|
106
|
+
'playbook.yaml',
|
|
107
|
+
] as const;
|
|
108
|
+
|
|
109
|
+
interface PendingInconclusiveDecision {
|
|
110
|
+
artifactFingerprint: string;
|
|
111
|
+
report: Awaited<ReturnType<typeof runLiveSemanticVerification>>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
interface CompileVerificationState {
|
|
115
|
+
excludedMs: number;
|
|
116
|
+
activeSinceMs?: number;
|
|
117
|
+
activeUntilMs?: number;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function readCompileVerificationState(toolDir: string): CompileVerificationState {
|
|
121
|
+
try {
|
|
122
|
+
const parsed = JSON.parse(
|
|
123
|
+
readFileSync(pathJoin(toolDir, VERIFICATION_STATE_SENTINEL), 'utf8'),
|
|
124
|
+
) as Partial<CompileVerificationState>;
|
|
125
|
+
return {
|
|
126
|
+
excludedMs:
|
|
127
|
+
typeof parsed.excludedMs === 'number' && Number.isFinite(parsed.excludedMs)
|
|
128
|
+
? Math.max(0, parsed.excludedMs)
|
|
129
|
+
: 0,
|
|
130
|
+
activeSinceMs:
|
|
131
|
+
typeof parsed.activeSinceMs === 'number' && Number.isFinite(parsed.activeSinceMs)
|
|
132
|
+
? parsed.activeSinceMs
|
|
133
|
+
: undefined,
|
|
134
|
+
activeUntilMs:
|
|
135
|
+
typeof parsed.activeUntilMs === 'number' && Number.isFinite(parsed.activeUntilMs)
|
|
136
|
+
? parsed.activeUntilMs
|
|
137
|
+
: undefined,
|
|
138
|
+
};
|
|
139
|
+
} catch {
|
|
140
|
+
return { excludedMs: 0 };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function writeCompileVerificationState(toolDir: string, state: CompileVerificationState): void {
|
|
145
|
+
const destination = pathJoin(toolDir, VERIFICATION_STATE_SENTINEL);
|
|
146
|
+
const temporary = pathJoin(
|
|
147
|
+
toolDir,
|
|
148
|
+
`${VERIFICATION_STATE_SENTINEL}.${process.pid}.${randomUUID()}.tmp`,
|
|
149
|
+
);
|
|
150
|
+
try {
|
|
151
|
+
writeFileSync(temporary, `${JSON.stringify(state)}\n`, {
|
|
152
|
+
encoding: 'utf8',
|
|
153
|
+
flag: 'wx',
|
|
154
|
+
mode: 0o600,
|
|
155
|
+
});
|
|
156
|
+
renameSync(temporary, destination);
|
|
157
|
+
} finally {
|
|
158
|
+
// rename removes the source on success; clean up a partial temp file if a
|
|
159
|
+
// write or rename fails without disturbing the last complete state.
|
|
160
|
+
if (existsSync(temporary)) unlinkSync(temporary);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function beginCompileVerification(toolDir: string): () => void {
|
|
165
|
+
const previous = readCompileVerificationState(toolDir);
|
|
166
|
+
const startedAt = Date.now();
|
|
167
|
+
writeCompileVerificationState(toolDir, {
|
|
168
|
+
excludedMs: previous.excludedMs,
|
|
169
|
+
activeSinceMs: startedAt,
|
|
170
|
+
activeUntilMs: startedAt + MAX_VERIFICATION_WALL_TIME_MS,
|
|
171
|
+
});
|
|
172
|
+
return () => {
|
|
173
|
+
const finishedAt = Date.now();
|
|
174
|
+
writeCompileVerificationState(toolDir, {
|
|
175
|
+
excludedMs:
|
|
176
|
+
previous.excludedMs +
|
|
177
|
+
Math.min(MAX_VERIFICATION_WALL_TIME_MS, Math.max(0, finishedAt - startedAt)),
|
|
178
|
+
});
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Effective compiler deadline after excluding bounded time spent inside done()
|
|
183
|
+
* verification. Exported for the CLI watchdogs and focused lifecycle tests. */
|
|
184
|
+
export function compileDeadlineAfterVerification(
|
|
185
|
+
toolDir: string,
|
|
186
|
+
compilerDeadlineMs: number,
|
|
187
|
+
now = Date.now(),
|
|
188
|
+
): number {
|
|
189
|
+
const state = readCompileVerificationState(toolDir);
|
|
190
|
+
const activeElapsedMs =
|
|
191
|
+
state.activeSinceMs === undefined
|
|
192
|
+
? 0
|
|
193
|
+
: Math.max(
|
|
194
|
+
0,
|
|
195
|
+
Math.min(now, state.activeUntilMs ?? state.activeSinceMs) - state.activeSinceMs,
|
|
196
|
+
);
|
|
197
|
+
return compilerDeadlineMs + state.excludedMs + activeElapsedMs;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function compileArtifactFingerprint(toolDir: string): string {
|
|
201
|
+
const hash = createHash('sha256');
|
|
202
|
+
for (const relativePath of COMPILE_ARTIFACT_FILES) {
|
|
203
|
+
const absolutePath = pathJoin(toolDir, relativePath);
|
|
204
|
+
hash.update(relativePath);
|
|
205
|
+
hash.update('\0');
|
|
206
|
+
if (existsSync(absolutePath)) hash.update(readFileSync(absolutePath));
|
|
207
|
+
hash.update('\0');
|
|
208
|
+
}
|
|
209
|
+
return hash.digest('hex');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function canAcceptInconclusiveDecision(opts: {
|
|
213
|
+
pendingFingerprint?: string;
|
|
214
|
+
currentFingerprint: string;
|
|
215
|
+
acceptInconclusive?: boolean;
|
|
216
|
+
inconclusiveReason?: string;
|
|
217
|
+
}): boolean {
|
|
218
|
+
return (
|
|
219
|
+
opts.acceptInconclusive === true &&
|
|
220
|
+
Boolean(opts.inconclusiveReason?.trim()) &&
|
|
221
|
+
opts.pendingFingerprint === opts.currentFingerprint
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
79
225
|
export async function runCompileMcpServer(opts: RunCompileMcpServerOptions): Promise<void> {
|
|
80
226
|
const isAuthMode = !!opts.authToolPlan;
|
|
81
|
-
const maxVerificationCycles = opts.maxVerificationCycles ??
|
|
227
|
+
const maxVerificationCycles = opts.maxVerificationCycles ?? 5;
|
|
228
|
+
let pendingInconclusive: PendingInconclusiveDecision | undefined;
|
|
82
229
|
|
|
83
230
|
// Load + auto-redact the session, exactly as compile-agent.ts does.
|
|
84
231
|
let session: Session = loadJsonFile(
|
|
@@ -110,9 +257,8 @@ export async function runCompileMcpServer(opts: RunCompileMcpServerOptions): Pro
|
|
|
110
257
|
|
|
111
258
|
if (isAuthMode) {
|
|
112
259
|
const site = opts.site ?? session.site;
|
|
113
|
-
// Credentials power the live
|
|
114
|
-
//
|
|
115
|
-
// out of argv.
|
|
260
|
+
// Credentials power the orchestrator-owned live verifier. Load them here
|
|
261
|
+
// rather than passing secrets on the command line.
|
|
116
262
|
const creds = await loadCredentialStore(site);
|
|
117
263
|
const teachCredentials = { site, values: creds?.values ?? {} };
|
|
118
264
|
compileTools = buildAuthCompileTools(session, opts.toolDir, opts.sessionPath, teachCredentials);
|
|
@@ -123,6 +269,7 @@ export async function runCompileMcpServer(opts: RunCompileMcpServerOptions): Pro
|
|
|
123
269
|
sharedContext: opts.sharedContext,
|
|
124
270
|
buildPlanPath: opts.buildPlanPath,
|
|
125
271
|
sharedModules: opts.sharedModules,
|
|
272
|
+
revisionMode: opts.revisionMode,
|
|
126
273
|
});
|
|
127
274
|
|
|
128
275
|
// Resolve the shared modules + producer→consumer token contracts + the general
|
|
@@ -146,6 +293,16 @@ export async function runCompileMcpServer(opts: RunCompileMcpServerOptions): Pro
|
|
|
146
293
|
type: 'object',
|
|
147
294
|
properties: {
|
|
148
295
|
summary: { type: 'string', description: 'Brief summary of what was accomplished' },
|
|
296
|
+
accept_inconclusive: {
|
|
297
|
+
type: 'boolean',
|
|
298
|
+
description:
|
|
299
|
+
'After an infrastructure-only inconclusive verifier result, explicitly ship the unchanged deterministic artifact without a liveVerified stamp.',
|
|
300
|
+
},
|
|
301
|
+
inconclusive_reason: {
|
|
302
|
+
type: 'string',
|
|
303
|
+
description:
|
|
304
|
+
'Compiler reasoning for accepting the unchanged artifact after reviewing the inconclusive verifier evidence.',
|
|
305
|
+
},
|
|
149
306
|
},
|
|
150
307
|
required: ['summary'],
|
|
151
308
|
},
|
|
@@ -176,20 +333,53 @@ export async function runCompileMcpServer(opts: RunCompileMcpServerOptions): Pro
|
|
|
176
333
|
{
|
|
177
334
|
name: 'run_verification',
|
|
178
335
|
description:
|
|
179
|
-
'
|
|
336
|
+
'Run one action from the current workflow.json live in the verification browser. Supply the action name and only its declared parameters. Set freshSession only when observed evidence means the prior browser and continuation state must be discarded. Set cleanSession only when evidence requires also withholding stored cookies and browser storage. After calling this, stop; the orchestrator resumes you with the observed result.',
|
|
180
337
|
inputSchema: {
|
|
181
338
|
type: 'object',
|
|
182
339
|
properties: {
|
|
183
|
-
|
|
184
|
-
|
|
340
|
+
action: { type: 'string', description: 'An action declared in authConfig.actions.' },
|
|
341
|
+
parameters: {
|
|
342
|
+
type: 'object',
|
|
343
|
+
description:
|
|
344
|
+
'Scalar values required by this action, keyed by workflow parameter name.',
|
|
345
|
+
},
|
|
346
|
+
freshSession: {
|
|
347
|
+
type: 'boolean',
|
|
348
|
+
description:
|
|
349
|
+
'Close and discard the prior verification browser and continuation state before running this action. Defaults to false.',
|
|
350
|
+
},
|
|
351
|
+
cleanSession: {
|
|
352
|
+
type: 'boolean',
|
|
353
|
+
description:
|
|
354
|
+
'Also withhold stored cookies and browser storage for this fresh run. Credential values remain available.',
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
required: ['action'],
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
name: 'inspect_verification_page',
|
|
362
|
+
description:
|
|
363
|
+
'Inspect the currently rendered page in the existing verification browser without navigating, resetting state, or running another auth action. Returns rendered body text, final URL, title, and optional cookie metadata (never cookie values). Use when observed verification evidence warrants inspecting the page. After calling this, stop; the orchestrator resumes you with the snapshot.',
|
|
364
|
+
inputSchema: {
|
|
365
|
+
type: 'object',
|
|
366
|
+
properties: {
|
|
367
|
+
maxChars: {
|
|
368
|
+
type: 'number',
|
|
369
|
+
description: 'Maximum rendered body-text characters to return (256–20000).',
|
|
370
|
+
},
|
|
371
|
+
includeCookies: {
|
|
372
|
+
type: 'boolean',
|
|
373
|
+
description:
|
|
374
|
+
'Include cookie names, domains, paths, expirations, and flags. Cookie values are never returned. Defaults to true.',
|
|
375
|
+
},
|
|
185
376
|
},
|
|
186
|
-
required: ['phase'],
|
|
187
377
|
},
|
|
188
378
|
},
|
|
189
379
|
{
|
|
190
380
|
name: 'prompt_user',
|
|
191
381
|
description:
|
|
192
|
-
'Ask the human
|
|
382
|
+
'Ask the human for input or an external action required by the compiled auth program. After calling this, stop; the orchestrator collects the answer and resumes you with it.',
|
|
193
383
|
inputSchema: {
|
|
194
384
|
type: 'object',
|
|
195
385
|
properties: {
|
|
@@ -206,7 +396,7 @@ export async function runCompileMcpServer(opts: RunCompileMcpServerOptions): Pro
|
|
|
206
396
|
{
|
|
207
397
|
name: 'wait_for_cooldown',
|
|
208
398
|
description:
|
|
209
|
-
'
|
|
399
|
+
'Request a delay without running any live action. Use only when the observed site response justifies waiting. After calling this, stop.',
|
|
210
400
|
inputSchema: {
|
|
211
401
|
type: 'object',
|
|
212
402
|
properties: {
|
|
@@ -233,7 +423,7 @@ export async function runCompileMcpServer(opts: RunCompileMcpServerOptions): Pro
|
|
|
233
423
|
{
|
|
234
424
|
capabilities: { tools: {} },
|
|
235
425
|
instructions: isAuthMode
|
|
236
|
-
? '
|
|
426
|
+
? '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.'
|
|
237
427
|
: '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.',
|
|
238
428
|
},
|
|
239
429
|
);
|
|
@@ -259,7 +449,12 @@ export async function runCompileMcpServer(opts: RunCompileMcpServerOptions): Pro
|
|
|
259
449
|
|
|
260
450
|
// Custom done — runs verification inline.
|
|
261
451
|
if (name === 'done') {
|
|
262
|
-
const
|
|
452
|
+
const doneArgs = args as {
|
|
453
|
+
summary?: string;
|
|
454
|
+
accept_inconclusive?: boolean;
|
|
455
|
+
inconclusive_reason?: string;
|
|
456
|
+
};
|
|
457
|
+
const summary = doneArgs.summary ?? 'Task completed';
|
|
263
458
|
log(`done() called: ${summary}`);
|
|
264
459
|
|
|
265
460
|
// Auth mode: lightweight structural verification (the agent already proved
|
|
@@ -268,6 +463,7 @@ export async function runCompileMcpServer(opts: RunCompileMcpServerOptions): Pro
|
|
|
268
463
|
const failures = authExternalVerification(
|
|
269
464
|
opts.toolDir,
|
|
270
465
|
(opts.authToolPlan?.captures ?? []).map((c) => ({ name: c.name, usedAs: c.usedAs })),
|
|
466
|
+
{ requireLiveAttempt: true },
|
|
271
467
|
);
|
|
272
468
|
if (failures.length === 0) {
|
|
273
469
|
const sentinel = pathJoin(opts.toolDir, DONE_SENTINEL);
|
|
@@ -292,7 +488,201 @@ export async function runCompileMcpServer(opts: RunCompileMcpServerOptions): Pro
|
|
|
292
488
|
}
|
|
293
489
|
|
|
294
490
|
verificationFailures++;
|
|
295
|
-
log(`auth verification failed (cycle ${verificationFailures}
|
|
491
|
+
log(`auth verification failed (cycle ${verificationFailures})`);
|
|
492
|
+
|
|
493
|
+
return {
|
|
494
|
+
isError: true,
|
|
495
|
+
content: [
|
|
496
|
+
{
|
|
497
|
+
type: 'text',
|
|
498
|
+
text: `You called done but verification failed (cycle ${verificationFailures}):
|
|
499
|
+
|
|
500
|
+
${failures.map((f) => `- ${f}`).join('\n')}
|
|
501
|
+
|
|
502
|
+
Fix the issues in workflow.json, re-test with run_verification, and call done again when fixed.`,
|
|
503
|
+
},
|
|
504
|
+
],
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const finishVerification = beginCompileVerification(opts.toolDir);
|
|
509
|
+
try {
|
|
510
|
+
const { failures, warnings, paramVerification, integrationEvidence } =
|
|
511
|
+
await externalVerification(opts.toolDir, session, opts.sessionPath, {
|
|
512
|
+
expectedToolName: opts.candidate?.toolName,
|
|
513
|
+
likelyParams: opts.candidate?.likelyParams,
|
|
514
|
+
candidateRequestSeqs: opts.candidate?.requestSeqs,
|
|
515
|
+
// Widen Fix B's variation pool to dependency requests so a token that
|
|
516
|
+
// varies only across them and is frozen as a literal in the tool's
|
|
517
|
+
// request is caught (the cross-request session-token leak case).
|
|
518
|
+
dependencyRequestSeqs: opts.candidate?.dependencySeqs,
|
|
519
|
+
assignedSharedModules,
|
|
520
|
+
tokenParams,
|
|
521
|
+
tokenParamShapes,
|
|
522
|
+
emittedTokens,
|
|
523
|
+
requiredInputs,
|
|
524
|
+
credentialValues,
|
|
525
|
+
credentialNames: opts.sharedContext?.credentialNames,
|
|
526
|
+
deferLiveIntegrationToSemanticAgent: true,
|
|
527
|
+
});
|
|
528
|
+
if (warnings.length > 0) {
|
|
529
|
+
log(`verification warnings (non-blocking):\n${warnings.join('\n')}`);
|
|
530
|
+
}
|
|
531
|
+
if (failures.length === 0) {
|
|
532
|
+
if (!opts.provider) {
|
|
533
|
+
failures.push('compile provider was not supplied to the independent semantic verifier');
|
|
534
|
+
} else {
|
|
535
|
+
const currentFingerprint = compileArtifactFingerprint(opts.toolDir);
|
|
536
|
+
if (
|
|
537
|
+
pendingInconclusive &&
|
|
538
|
+
canAcceptInconclusiveDecision({
|
|
539
|
+
pendingFingerprint: pendingInconclusive.artifactFingerprint,
|
|
540
|
+
currentFingerprint,
|
|
541
|
+
acceptInconclusive: doneArgs.accept_inconclusive,
|
|
542
|
+
inconclusiveReason: doneArgs.inconclusive_reason,
|
|
543
|
+
})
|
|
544
|
+
) {
|
|
545
|
+
const semantic = pendingInconclusive.report;
|
|
546
|
+
const semanticParams = mergeSemanticParamVerification(
|
|
547
|
+
paramVerification,
|
|
548
|
+
semantic.report,
|
|
549
|
+
);
|
|
550
|
+
const inconclusiveReason = doneArgs.inconclusive_reason?.trim() ?? '';
|
|
551
|
+
applyLiveVerification(opts.toolDir, {
|
|
552
|
+
kind: 'waived-infra',
|
|
553
|
+
firstError: inconclusiveReason,
|
|
554
|
+
exhaustedBackends: [],
|
|
555
|
+
});
|
|
556
|
+
const paramWarnings = applyParamVerification(opts.toolDir, semanticParams);
|
|
557
|
+
// The unverified stamp and parameter annotations are metadata-only,
|
|
558
|
+
// so carry the already-proven preference to the final workflow hash.
|
|
559
|
+
rebindExistingBackendsCacheToWorkflow(opts.toolDir);
|
|
560
|
+
const allWarnings = [
|
|
561
|
+
...warnings,
|
|
562
|
+
`independent semantic verification was inconclusive: ${semantic.report.summary}`,
|
|
563
|
+
...paramWarnings,
|
|
564
|
+
];
|
|
565
|
+
const sentinel = pathJoin(opts.toolDir, DONE_SENTINEL);
|
|
566
|
+
writeFileSync(
|
|
567
|
+
sentinel,
|
|
568
|
+
JSON.stringify(
|
|
569
|
+
{
|
|
570
|
+
summary,
|
|
571
|
+
verification: 'passed',
|
|
572
|
+
liveVerified: false,
|
|
573
|
+
semanticVerification: {
|
|
574
|
+
status: semantic.report.status,
|
|
575
|
+
provider: semantic.provider,
|
|
576
|
+
model: semantic.model,
|
|
577
|
+
attempts: semantic.attempts,
|
|
578
|
+
evidenceArtifact: semantic.report.evidenceArtifact,
|
|
579
|
+
logArtifact: semantic.report.logArtifact,
|
|
580
|
+
},
|
|
581
|
+
compilerDecision: {
|
|
582
|
+
decision: 'ship_unverified',
|
|
583
|
+
reason: inconclusiveReason,
|
|
584
|
+
},
|
|
585
|
+
warnings: allWarnings,
|
|
586
|
+
timestamp: Date.now(),
|
|
587
|
+
},
|
|
588
|
+
null,
|
|
589
|
+
2,
|
|
590
|
+
),
|
|
591
|
+
'utf8',
|
|
592
|
+
);
|
|
593
|
+
log(`compiler accepted unchanged inconclusive artifact; wrote ${sentinel}`);
|
|
594
|
+
return {
|
|
595
|
+
content: [
|
|
596
|
+
{
|
|
597
|
+
type: 'text',
|
|
598
|
+
text: `DONE_UNVERIFIED — deterministic verification passed and the compiler explicitly accepted the unchanged artifact after an infrastructure-only inconclusive live review. Evidence: ${semantic.report.evidenceArtifact ?? '.live-verification-evidence.json'}. Verifier log: ${semantic.report.logArtifact ?? '.live-verifier-log.jsonl'}. The orchestrator will exit shortly. Do not call any more tools.`,
|
|
599
|
+
},
|
|
600
|
+
],
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (
|
|
605
|
+
pendingInconclusive &&
|
|
606
|
+
pendingInconclusive.artifactFingerprint !== currentFingerprint
|
|
607
|
+
) {
|
|
608
|
+
log(
|
|
609
|
+
'compile artifacts changed after inconclusive review; running verification again',
|
|
610
|
+
);
|
|
611
|
+
pendingInconclusive = undefined;
|
|
612
|
+
}
|
|
613
|
+
const semantic = await runLiveSemanticVerification({
|
|
614
|
+
provider: opts.provider,
|
|
615
|
+
toolDir: opts.toolDir,
|
|
616
|
+
evidence: integrationEvidence,
|
|
617
|
+
});
|
|
618
|
+
if (semantic.report.status === 'inconclusive') {
|
|
619
|
+
pendingInconclusive = {
|
|
620
|
+
artifactFingerprint: compileArtifactFingerprint(opts.toolDir),
|
|
621
|
+
report: semantic,
|
|
622
|
+
};
|
|
623
|
+
} else {
|
|
624
|
+
pendingInconclusive = undefined;
|
|
625
|
+
}
|
|
626
|
+
const semanticFailures = semanticVerificationFailures(semantic.report);
|
|
627
|
+
failures.push(...semanticFailures);
|
|
628
|
+
if (semantic.report.status === 'approved_with_gaps') {
|
|
629
|
+
warnings.push(
|
|
630
|
+
`independent semantic verification approved with gaps: ${semantic.report.gaps.join('; ')}`,
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
if (semanticFailures.length === 0) {
|
|
634
|
+
const semanticParams = mergeSemanticParamVerification(
|
|
635
|
+
paramVerification,
|
|
636
|
+
semantic.report,
|
|
637
|
+
);
|
|
638
|
+
// Only the independent semantic agent can mint liveVerified=true.
|
|
639
|
+
applyLiveVerification(opts.toolDir, undefined);
|
|
640
|
+
const paramWarnings = applyParamVerification(opts.toolDir, semanticParams);
|
|
641
|
+
// liveVerified/parameter annotations are metadata-only, but they
|
|
642
|
+
// change the hash-strict workflow cache key. Keep the verifier's
|
|
643
|
+
// already-proven backend preference current without probing again.
|
|
644
|
+
rebindExistingBackendsCacheToWorkflow(opts.toolDir);
|
|
645
|
+
if (paramWarnings.length > 0) {
|
|
646
|
+
log(`parameter verification:\n${paramWarnings.join('\n')}`);
|
|
647
|
+
}
|
|
648
|
+
const allWarnings = [...warnings, ...paramWarnings];
|
|
649
|
+
const sentinel = pathJoin(opts.toolDir, DONE_SENTINEL);
|
|
650
|
+
writeFileSync(
|
|
651
|
+
sentinel,
|
|
652
|
+
JSON.stringify(
|
|
653
|
+
{
|
|
654
|
+
summary,
|
|
655
|
+
verification: 'passed',
|
|
656
|
+
semanticVerification: {
|
|
657
|
+
status: semantic.report.status,
|
|
658
|
+
provider: semantic.provider,
|
|
659
|
+
model: semantic.model,
|
|
660
|
+
attempts: semantic.attempts,
|
|
661
|
+
evidenceArtifact: semantic.report.evidenceArtifact,
|
|
662
|
+
logArtifact: semantic.report.logArtifact,
|
|
663
|
+
},
|
|
664
|
+
warnings: allWarnings,
|
|
665
|
+
timestamp: Date.now(),
|
|
666
|
+
},
|
|
667
|
+
null,
|
|
668
|
+
2,
|
|
669
|
+
),
|
|
670
|
+
'utf8',
|
|
671
|
+
);
|
|
672
|
+
log(`verification passed; wrote ${sentinel}`);
|
|
673
|
+
return {
|
|
674
|
+
content: [
|
|
675
|
+
{
|
|
676
|
+
type: 'text',
|
|
677
|
+
text: `DONE_VERIFIED — deterministic checks and independent live semantic verification passed (${semantic.report.status}). Evidence: ${semantic.report.evidenceArtifact ?? '.live-verification-evidence.json'}. Verifier log: ${semantic.report.logArtifact ?? '.live-verifier-log.jsonl'}. The orchestrator will exit shortly. Do not call any more tools.`,
|
|
678
|
+
},
|
|
679
|
+
],
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
verificationFailures++;
|
|
685
|
+
log(`verification failed (cycle ${verificationFailures}/${maxVerificationCycles})`);
|
|
296
686
|
if (verificationFailures >= maxVerificationCycles) {
|
|
297
687
|
const sentinel = pathJoin(opts.toolDir, DONE_SENTINEL);
|
|
298
688
|
writeFileSync(
|
|
@@ -303,6 +693,7 @@ export async function runCompileMcpServer(opts: RunCompileMcpServerOptions): Pro
|
|
|
303
693
|
verification: 'failed',
|
|
304
694
|
cycles: verificationFailures,
|
|
305
695
|
failures,
|
|
696
|
+
warnings,
|
|
306
697
|
timestamp: Date.now(),
|
|
307
698
|
},
|
|
308
699
|
null,
|
|
@@ -321,116 +712,57 @@ export async function runCompileMcpServer(opts: RunCompileMcpServerOptions): Pro
|
|
|
321
712
|
};
|
|
322
713
|
}
|
|
323
714
|
|
|
324
|
-
|
|
325
|
-
isError: true,
|
|
326
|
-
content: [
|
|
327
|
-
{
|
|
328
|
-
type: 'text',
|
|
329
|
-
text: `You called done but verification failed (cycle ${verificationFailures}/${maxVerificationCycles}):
|
|
715
|
+
const continuationMessage = `You called done but verification failed (cycle ${verificationFailures}/${maxVerificationCycles}):
|
|
330
716
|
|
|
331
717
|
${failures.map((f) => `- ${f}`).join('\n')}
|
|
332
718
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
const { failures, warnings, paramVerification, liveVerification } =
|
|
340
|
-
await externalVerification(opts.toolDir, session, opts.sessionPath, {
|
|
341
|
-
expectedToolName: opts.candidate?.toolName,
|
|
342
|
-
likelyParams: opts.candidate?.likelyParams,
|
|
343
|
-
candidateRequestSeqs: opts.candidate?.requestSeqs,
|
|
344
|
-
// Widen Fix B's variation pool to dependency requests so a token that
|
|
345
|
-
// varies only across them and is frozen as a literal in the tool's
|
|
346
|
-
// request is caught (the cross-request session-token leak case).
|
|
347
|
-
dependencyRequestSeqs: opts.candidate?.dependencySeqs,
|
|
348
|
-
assignedSharedModules,
|
|
349
|
-
tokenParams,
|
|
350
|
-
tokenParamShapes,
|
|
351
|
-
emittedTokens,
|
|
352
|
-
requiredInputs,
|
|
353
|
-
credentialValues,
|
|
354
|
-
credentialNames: opts.sharedContext?.credentialNames,
|
|
355
|
-
});
|
|
356
|
-
if (warnings.length > 0) {
|
|
357
|
-
log(`verification warnings (non-blocking):\n${warnings.join('\n')}`);
|
|
358
|
-
}
|
|
359
|
-
if (failures.length === 0) {
|
|
360
|
-
// Persist per-parameter verified flags + the live-verification stamp
|
|
361
|
-
// onto workflow.json. Audit and teach read the stamp.
|
|
362
|
-
applyLiveVerification(opts.toolDir, liveVerification);
|
|
363
|
-
const paramWarnings = applyParamVerification(opts.toolDir, paramVerification);
|
|
364
|
-
if (paramWarnings.length > 0) {
|
|
365
|
-
log(`parameter verification:\n${paramWarnings.join('\n')}`);
|
|
366
|
-
}
|
|
367
|
-
const allWarnings = [...warnings, ...paramWarnings];
|
|
368
|
-
const sentinel = pathJoin(opts.toolDir, DONE_SENTINEL);
|
|
369
|
-
writeFileSync(
|
|
370
|
-
sentinel,
|
|
371
|
-
JSON.stringify(
|
|
372
|
-
{ summary, verification: 'passed', warnings: allWarnings, timestamp: Date.now() },
|
|
373
|
-
null,
|
|
374
|
-
2,
|
|
375
|
-
),
|
|
376
|
-
'utf8',
|
|
377
|
-
);
|
|
378
|
-
log(`verification passed; wrote ${sentinel}`);
|
|
379
|
-
return {
|
|
380
|
-
content: [
|
|
381
|
-
{
|
|
382
|
-
type: 'text',
|
|
383
|
-
text: 'DONE_VERIFIED — verification passed. The orchestrator will exit shortly. Do not call any more tools.',
|
|
384
|
-
},
|
|
385
|
-
],
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
verificationFailures++;
|
|
390
|
-
log(`verification failed (cycle ${verificationFailures}/${maxVerificationCycles})`);
|
|
391
|
-
if (verificationFailures >= maxVerificationCycles) {
|
|
392
|
-
const sentinel = pathJoin(opts.toolDir, DONE_SENTINEL);
|
|
393
|
-
writeFileSync(
|
|
394
|
-
sentinel,
|
|
395
|
-
JSON.stringify(
|
|
396
|
-
{
|
|
397
|
-
summary,
|
|
398
|
-
verification: 'failed',
|
|
399
|
-
cycles: verificationFailures,
|
|
400
|
-
failures,
|
|
401
|
-
warnings,
|
|
402
|
-
timestamp: Date.now(),
|
|
403
|
-
},
|
|
404
|
-
null,
|
|
405
|
-
2,
|
|
406
|
-
),
|
|
407
|
-
'utf8',
|
|
408
|
-
);
|
|
719
|
+
Resume your work. Read the files you wrote (workflow.json, parser.ts, parser.test.ts), fix the issues, re-run tests, and call done again when fixed.`;
|
|
720
|
+
const compilerDecisionGuidance = `\n\nUse the verifier's semantic evidence, not a requirement for 100% of the original candidate inputs. If the core intent works but a secondary parameter cannot be supported from the recording and live evidence without guessing, remove that parameter and add workflow.limitations with omittedParameters and a specific reason. Close that omission over its dependencies: also remove dependent public inputs and narrow parameter/intent descriptions so callers cannot still enter the unsupported branch indirectly. Do not use a limitation to hide a broken core tool, and do not keep a known broken or ignored public input.`;
|
|
721
|
+
const inconclusiveDecisionGuidance = pendingInconclusive
|
|
722
|
+
? '\n\nThe live verifier was infrastructure-only inconclusive. Review its evidence and log. If they expose a tool defect, revise the artifacts and call done normally so verification reruns. If they expose no tool-level defect and the deterministic artifact should ship explicitly unverified, call done again without changing compile artifacts, set accept_inconclusive=true, and provide your concrete reasoning in inconclusive_reason. This compiler decision is persisted and does not mint liveVerified.'
|
|
723
|
+
: '';
|
|
409
724
|
return {
|
|
410
725
|
isError: true,
|
|
411
726
|
content: [
|
|
412
727
|
{
|
|
413
728
|
type: 'text',
|
|
414
|
-
text:
|
|
729
|
+
text: continuationMessage + compilerDecisionGuidance + inconclusiveDecisionGuidance,
|
|
415
730
|
},
|
|
416
731
|
],
|
|
417
732
|
};
|
|
733
|
+
} finally {
|
|
734
|
+
finishVerification();
|
|
418
735
|
}
|
|
419
|
-
|
|
420
|
-
const continuationMessage = `You called done but verification failed (cycle ${verificationFailures}/${maxVerificationCycles}):
|
|
421
|
-
|
|
422
|
-
${failures.map((f) => `- ${f}`).join('\n')}
|
|
423
|
-
|
|
424
|
-
Resume your work. Read the files you wrote (workflow.json, parser.ts, parser.test.ts), fix the issues, re-run tests, and call done again when fixed.`;
|
|
425
|
-
return {
|
|
426
|
-
isError: true,
|
|
427
|
-
content: [{ type: 'text', text: continuationMessage }],
|
|
428
|
-
};
|
|
429
736
|
}
|
|
430
737
|
|
|
431
738
|
// Auth-mode checkpoint tools — record the request and end the segment. The
|
|
432
739
|
// orchestrator performs the action live and resumes the agent with the result.
|
|
433
|
-
if (
|
|
740
|
+
if (
|
|
741
|
+
name === 'run_verification' ||
|
|
742
|
+
name === 'inspect_verification_page' ||
|
|
743
|
+
name === 'prompt_user' ||
|
|
744
|
+
name === 'wait_for_cooldown'
|
|
745
|
+
) {
|
|
746
|
+
if (name === 'run_verification') {
|
|
747
|
+
const failures = authWorkflowPreflightFailures(
|
|
748
|
+
opts.toolDir,
|
|
749
|
+
session,
|
|
750
|
+
opts.authToolPlan?.credentialNames,
|
|
751
|
+
);
|
|
752
|
+
if (failures.length > 0) {
|
|
753
|
+
return {
|
|
754
|
+
isError: true,
|
|
755
|
+
content: [
|
|
756
|
+
{
|
|
757
|
+
type: 'text',
|
|
758
|
+
text: `run_verification blocked by auth workflow preflight. Fix workflow.json before requesting live verification:\n${failures
|
|
759
|
+
.map((failure) => `- ${failure}`)
|
|
760
|
+
.join('\n')}`,
|
|
761
|
+
},
|
|
762
|
+
],
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
}
|
|
434
766
|
if (checkpointWritten) {
|
|
435
767
|
return {
|
|
436
768
|
isError: true,
|
|
@@ -524,4 +856,5 @@ export const COMPILE_SENTINELS = {
|
|
|
524
856
|
done: DONE_SENTINEL,
|
|
525
857
|
giveUp: GIVE_UP_SENTINEL,
|
|
526
858
|
checkpoint: CHECKPOINT_SENTINEL,
|
|
859
|
+
verificationState: VERIFICATION_STATE_SENTINEL,
|
|
527
860
|
} as const;
|