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