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.
Files changed (58) hide show
  1. package/README.md +66 -2
  2. package/package.json +1 -1
  3. package/prompts/auth-compile-agent.md +173 -22
  4. package/prompts/compile-agent.md +45 -8
  5. package/prompts/component-recovery.md +40 -0
  6. package/prompts/master-teach-decision.md +28 -3
  7. package/prompts/master-teach-focused-planner.md +18 -5
  8. package/src/cli.ts +26 -18
  9. package/src/imprint/agent-tool.ts +15 -0
  10. package/src/imprint/agent.ts +2 -11
  11. package/src/imprint/audit.ts +35 -13
  12. package/src/imprint/auth-browser-discovery.ts +276 -0
  13. package/src/imprint/auth-compile-agent.ts +513 -224
  14. package/src/imprint/auth-compile-tools.ts +213 -25
  15. package/src/imprint/auth-discovery-evidence.ts +81 -0
  16. package/src/imprint/auth-plan.ts +28 -0
  17. package/src/imprint/auth-verifier.ts +234 -20
  18. package/src/imprint/authentication-interaction.ts +137 -0
  19. package/src/imprint/backend-ladder.ts +271 -43
  20. package/src/imprint/build-plan.ts +2 -22
  21. package/src/imprint/cdp-browser-fetch.ts +242 -60
  22. package/src/imprint/chromium.ts +93 -29
  23. package/src/imprint/claude-cli-compile.ts +2 -0
  24. package/src/imprint/codex-cli-compile.ts +99 -46
  25. package/src/imprint/compile-agent-types.ts +9 -5
  26. package/src/imprint/compile-agent.ts +33 -0
  27. package/src/imprint/compile-tool-executor.ts +783 -0
  28. package/src/imprint/compile-tools.ts +42 -9
  29. package/src/imprint/compile-verification.ts +2 -0
  30. package/src/imprint/compile.ts +3 -0
  31. package/src/imprint/compiler-log.ts +16 -0
  32. package/src/imprint/component-recovery.ts +120 -0
  33. package/src/imprint/cookie-jar.ts +117 -7
  34. package/src/imprint/credential-extract.ts +9 -2
  35. package/src/imprint/credential-lock.ts +147 -0
  36. package/src/imprint/credential-observation.ts +261 -0
  37. package/src/imprint/credential-store.ts +284 -44
  38. package/src/imprint/live-component-recovery.ts +73 -0
  39. package/src/imprint/llm.ts +4 -3
  40. package/src/imprint/log.ts +16 -1
  41. package/src/imprint/login.ts +9 -0
  42. package/src/imprint/master-teach-agent-contracts.ts +40 -3
  43. package/src/imprint/master-teach-agents.ts +229 -14
  44. package/src/imprint/master-teach-checks.ts +8 -1
  45. package/src/imprint/master-teach-controller.ts +681 -138
  46. package/src/imprint/master-teach-prompt-projections.ts +2 -0
  47. package/src/imprint/mcp-cdp-sessions.ts +7 -2
  48. package/src/imprint/mcp-compile-server.ts +62 -850
  49. package/src/imprint/mcp-server.ts +667 -91
  50. package/src/imprint/mcp-session-recovery.ts +167 -0
  51. package/src/imprint/native-teach-agents.ts +232 -108
  52. package/src/imprint/runtime.ts +416 -47
  53. package/src/imprint/stealth-chromium.ts +67 -7
  54. package/src/imprint/teach-browser-resources.ts +88 -0
  55. package/src/imprint/teach-stage-progress.ts +38 -0
  56. package/src/imprint/terminal-user-prompt.ts +42 -0
  57. package/src/imprint/types.ts +51 -18
  58. package/src/imprint/user-interaction.ts +836 -0
@@ -7,7 +7,10 @@
7
7
  import { createHash } from 'node:crypto';
8
8
  import { existsSync, readFileSync } from 'node:fs';
9
9
  import { join as pathJoin } from 'node:path';
10
+ import { z } from 'zod';
10
11
  import type { AgentTool } from './agent.ts';
12
+ import { AuthBrowserActionSchema } from './auth-browser-discovery.ts';
13
+ import type { AuthCheckpoint, AuthCheckpointResult } from './compile-agent-types.ts';
11
14
  import {
12
15
  type CompileToolContext,
13
16
  bodyEncodingContractFailures,
@@ -24,7 +27,13 @@ import {
24
27
  import { isIrreversibleRequest, workflowHasIrreversibleEffect } from './effects.ts';
25
28
  import { recordedRequestMatchesWorkflow } from './recording-request.ts';
26
29
  import { captureHeader, captureValueMatches, resolveJsonCapture } from './request-capture.ts';
27
- import { type Session, type Workflow, WorkflowSchema, persistedCaptureName } from './types.ts';
30
+ import {
31
+ type Session,
32
+ type Workflow,
33
+ WorkflowSchema,
34
+ authActionOutcomes,
35
+ persistedCaptureName,
36
+ } from './types.ts';
28
37
 
29
38
  type TeachCredentials = { site: string; values: Record<string, string> };
30
39
 
@@ -32,9 +41,15 @@ export const AUTH_VERIFICATION_ATTEMPT_SENTINEL = '.auth-verification-attempt.js
32
41
 
33
42
  export interface AuthCliCompileMode {
34
43
  site: string;
44
+ contextRef?: string;
35
45
  authPlanJson: string;
36
46
  allowedTools: readonly string[];
37
47
  initialPrompt: string;
48
+ /** Host-only native/API execution; CLI-only drivers retain the sentinel fallback. */
49
+ executeCheckpoint?: (
50
+ checkpoint: AuthCheckpoint,
51
+ signal?: AbortSignal,
52
+ ) => Promise<AuthCheckpointResult>;
38
53
  }
39
54
 
40
55
  export const AUTH_COMPILE_TOOL_NAMES = [
@@ -47,10 +62,165 @@ export const AUTH_COMPILE_TOOL_NAMES = [
47
62
  'run_tests',
48
63
  'run_verification',
49
64
  'inspect_verification_page',
65
+ 'interact_verification_browser',
50
66
  'prompt_user',
51
67
  'wait_for_cooldown',
52
68
  ] as const;
53
69
 
70
+ export const AUTH_CHECKPOINT_TOOLS: Omit<AgentTool, 'handler'>[] = [
71
+ {
72
+ name: 'interact_verification_browser',
73
+ description:
74
+ 'Explore the live auth UI in the retained verification browser and capture its network requests/responses as additional readable recording evidence. Can open a browser before a workflow exists. Choose inspect, navigate, click, fill, select, press, or wait from observed page controls. For fill values use stored ${credential.NAME} or current ${prompt.ID} references; answers are resolved locally. Capture continues between calls and while prompting the human. This does not verify the compiled workflow.',
75
+ input_schema: {
76
+ type: 'object',
77
+ properties: {
78
+ action: {
79
+ type: 'string',
80
+ enum: ['inspect', 'navigate', 'click', 'fill', 'select', 'press', 'wait'],
81
+ },
82
+ url: { type: 'string', description: 'HTTP(S) destination for navigate.' },
83
+ selector: {
84
+ type: 'string',
85
+ description: 'Exact CSS selector from the observed UI for click/fill/select.',
86
+ },
87
+ value: {
88
+ type: 'string',
89
+ description:
90
+ 'Value for fill/select; secret inputs use credential or current prompt references.',
91
+ },
92
+ key: { type: 'string', description: 'Keyboard key for press, e.g. Enter or Tab.' },
93
+ milliseconds: { type: 'number', description: 'Observation wait duration, 0–30000 ms.' },
94
+ },
95
+ required: ['action'],
96
+ },
97
+ },
98
+ {
99
+ name: 'run_verification',
100
+ description:
101
+ 'Run one action from the current workflow.json live in the verification browser. First reuse the proven noninteractive session-check or context-restoration action before restarting login. Branch on current response evidence before delivering MFA. 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.',
102
+ input_schema: {
103
+ type: 'object',
104
+ properties: {
105
+ action: {
106
+ type: 'string',
107
+ description: 'An action declared in authConfig.actions.',
108
+ },
109
+ parameters: {
110
+ type: 'object',
111
+ description: 'Scalar values required by this action, keyed by workflow parameter name.',
112
+ },
113
+ freshSession: {
114
+ type: 'boolean',
115
+ description:
116
+ 'Close and discard the prior verification browser and continuation state before running this action. Defaults to false.',
117
+ },
118
+ cleanSession: {
119
+ type: 'boolean',
120
+ description:
121
+ 'Also withhold stored cookies and browser storage for this fresh run. Credential values remain available.',
122
+ },
123
+ },
124
+ required: ['action'],
125
+ },
126
+ },
127
+ {
128
+ name: 'inspect_verification_page',
129
+ description:
130
+ '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 cookies. Known user-supplied secrets are masked; other cookie values remain private evidence. Use when observed verification evidence warrants inspecting the page.',
131
+ input_schema: {
132
+ type: 'object',
133
+ properties: {
134
+ maxChars: {
135
+ type: 'number',
136
+ description: 'Maximum rendered body-text characters to return (256–20000).',
137
+ },
138
+ includeCookies: {
139
+ type: 'boolean',
140
+ description:
141
+ 'Include cookies with names, values, domains, paths, expirations, and flags. Known user-supplied secrets are masked. Defaults to true.',
142
+ },
143
+ },
144
+ },
145
+ },
146
+ {
147
+ name: 'prompt_user',
148
+ description:
149
+ 'Ask the human for input or an external action required by the compiled auth program. A selected fixed option is returned as an answered choice with its exact value; free-text answers remain current opaque response references. A choice is not proof that a live action succeeded.',
150
+ input_schema: {
151
+ type: 'object',
152
+ properties: {
153
+ message: { type: 'string', description: 'What to ask the user to do.' },
154
+ options: {
155
+ type: 'array',
156
+ items: { type: 'string' },
157
+ description: 'Optional fixed choices; omit for free-text (e.g. the OTP code).',
158
+ },
159
+ },
160
+ required: ['message'],
161
+ },
162
+ },
163
+ {
164
+ name: 'wait_for_cooldown',
165
+ description:
166
+ 'Request a delay without running any live action. Use only when the observed site response justifies waiting.',
167
+ input_schema: {
168
+ type: 'object',
169
+ properties: {
170
+ minutes: { type: 'number', description: 'Cool-off minutes (5–10 typical).' },
171
+ reason: {
172
+ type: 'string',
173
+ description: 'Why you believe it is a cool-off, not a defect.',
174
+ },
175
+ },
176
+ required: ['minutes'],
177
+ },
178
+ },
179
+ ];
180
+
181
+ /** Parse the same host request for inline and checkpoint/resume adapters. */
182
+ export function parseAuthCheckpoint(name: string, input: unknown): AuthCheckpoint {
183
+ switch (name) {
184
+ case 'interact_verification_browser':
185
+ return { kind: name, ...AuthBrowserActionSchema.parse(input) };
186
+ case 'run_verification':
187
+ return {
188
+ kind: name,
189
+ ...z
190
+ .object({
191
+ action: z.string().min(1),
192
+ parameters: z.record(z.union([z.string(), z.number(), z.boolean()])).optional(),
193
+ freshSession: z.boolean().optional(),
194
+ cleanSession: z.boolean().optional(),
195
+ })
196
+ .parse(input),
197
+ };
198
+ case 'inspect_verification_page':
199
+ return {
200
+ kind: name,
201
+ ...z
202
+ .object({ maxChars: z.number().optional(), includeCookies: z.boolean().optional() })
203
+ .parse(input),
204
+ };
205
+ case 'prompt_user':
206
+ return {
207
+ kind: name,
208
+ ...z
209
+ .object({ message: z.string().min(1), options: z.array(z.string()).optional() })
210
+ .parse(input),
211
+ };
212
+ case 'wait_for_cooldown':
213
+ return {
214
+ kind: name,
215
+ ...z
216
+ .object({ minutes: z.number().nonnegative(), reason: z.string().optional() })
217
+ .parse(input),
218
+ };
219
+ default:
220
+ throw new Error(`Unknown auth checkpoint: ${name}`);
221
+ }
222
+ }
223
+
54
224
  export function buildAuthCompileTools(
55
225
  session: Session,
56
226
  toolDir: string,
@@ -295,6 +465,18 @@ function authProgramFailures(
295
465
  }
296
466
  }
297
467
  }
468
+ for (const branch of action.branches ?? []) {
469
+ const capture = branch.when;
470
+ if (captures.has(capture.name)) {
471
+ failures.push(
472
+ `capture name ${JSON.stringify(capture.name)} must be unique across the auth workflow`,
473
+ );
474
+ }
475
+ captures.add(capture.name);
476
+ const lastStep = action.steps.at(-1);
477
+ const request = lastStep && workflow.requests[lastStep.request];
478
+ if (request) captureOwners.set(capture.name, { request, capture });
479
+ }
298
480
  }
299
481
 
300
482
  let hasSuccess = false;
@@ -318,28 +500,30 @@ function authProgramFailures(
318
500
  );
319
501
  }
320
502
  }
321
- for (const evidence of action.outcome.evidence) {
322
- if (!captures.has(evidence)) {
323
- failures.push(
324
- `action ${JSON.stringify(name)} references unknown evidence capture ${JSON.stringify(evidence)}`,
325
- );
326
- }
327
- }
328
- if (action.outcome.type === 'pause') {
329
- if (!config.actions[action.outcome.next]) {
330
- failures.push(
331
- `action ${JSON.stringify(name)} pauses to unknown action ${JSON.stringify(action.outcome.next)}`,
332
- );
503
+ for (const outcome of authActionOutcomes(action)) {
504
+ for (const evidence of outcome.evidence) {
505
+ if (!captures.has(evidence)) {
506
+ failures.push(
507
+ `action ${JSON.stringify(name)} references unknown evidence capture ${JSON.stringify(evidence)}`,
508
+ );
509
+ }
333
510
  }
334
- for (const carried of action.outcome.carry) {
335
- if (!captures.has(carried)) {
511
+ if (outcome.type === 'pause') {
512
+ if (!config.actions[outcome.next]) {
336
513
  failures.push(
337
- `action ${JSON.stringify(name)} carries unknown capture ${JSON.stringify(carried)}`,
514
+ `action ${JSON.stringify(name)} pauses to unknown action ${JSON.stringify(outcome.next)}`,
338
515
  );
339
516
  }
517
+ for (const carried of outcome.carry) {
518
+ if (!captures.has(carried)) {
519
+ failures.push(
520
+ `action ${JSON.stringify(name)} carries unknown capture ${JSON.stringify(carried)}`,
521
+ );
522
+ }
523
+ }
524
+ } else {
525
+ hasSuccess = true;
340
526
  }
341
- } else {
342
- hasSuccess = true;
343
527
  }
344
528
  }
345
529
 
@@ -428,13 +612,17 @@ function reachableSuccess(
428
612
  actions: NonNullable<Workflow['authConfig']>['actions'],
429
613
  ): boolean {
430
614
  const seen = new Set<string>();
431
- let current: string | undefined = entry;
432
- while (current && !seen.has(current)) {
615
+ const pending = [entry];
616
+ while (pending.length) {
617
+ const current = pending.pop();
618
+ if (!current || seen.has(current)) continue;
433
619
  seen.add(current);
434
- const action: (typeof actions)[string] | undefined = actions[current];
435
- if (!action) return false;
436
- if (action.outcome.type === 'success') return true;
437
- current = action.outcome.next;
620
+ const action = actions[current];
621
+ if (!action) continue;
622
+ for (const outcome of authActionOutcomes(action)) {
623
+ if (outcome.type === 'success') return true;
624
+ pending.push(outcome.next);
625
+ }
438
626
  }
439
627
  return false;
440
628
  }
@@ -501,7 +689,7 @@ export function readAuthVerificationReceiptStatus(
501
689
  };
502
690
  }
503
691
  const action = workflow.authConfig?.actions[attempt.action];
504
- if (action?.outcome.type !== 'success') {
692
+ if (!action || !authActionOutcomes(action).some((outcome) => outcome.type === 'success')) {
505
693
  return {
506
694
  status: 'failed',
507
695
  reason: `the most recent successful live action ${JSON.stringify(attempt.action)} does not declare a success outcome`,
@@ -0,0 +1,81 @@
1
+ import { join } from 'node:path';
2
+ import type { AuthBrowserObservation } from './auth-browser-discovery.ts';
3
+ import { writePrivateFile } from './private-file.ts';
4
+ import type { Session } from './types.ts';
5
+
6
+ /** A run-local working copy. Original demonstrations are never overwritten.
7
+ * Live requests use the ordinary recording readers/tests and provenance fields. */
8
+ export class AuthDiscoveryEvidence {
9
+ readonly session: Session;
10
+ readonly sessionPath: string;
11
+ private readonly identities = new Map<string, number>();
12
+ private nextSeq: number;
13
+
14
+ constructor(
15
+ session: Session,
16
+ private readonly toolDir: string,
17
+ ) {
18
+ this.session = structuredClone(session);
19
+ this.nextSeq = Math.max(-1, ...session.requests.map((r) => r.seq)) + 1;
20
+ this.sessionPath = join(toolDir, '.auth-evidence-session.json');
21
+ this.save();
22
+ }
23
+
24
+ append(observation: AuthBrowserObservation): string {
25
+ const observedAt = new Date().toISOString();
26
+ const timestamp = Date.now() - Date.parse(this.session.startedAt);
27
+ const index = observation.requests.map((request) => {
28
+ const identity = `${observation.sourceId}:${request.seq}`;
29
+ let seq = this.identities.get(identity);
30
+ if (seq === undefined) {
31
+ seq = this.nextSeq++;
32
+ this.identities.set(identity, seq);
33
+ }
34
+ const prior = this.session.requests.findIndex((r) => r.seq === seq);
35
+ const captured = {
36
+ ...request,
37
+ seq,
38
+ timestamp: this.session.requests[prior]?.timestamp ?? timestamp,
39
+ };
40
+ if (prior < 0) this.session.requests.push(captured);
41
+ else this.session.requests[prior] = captured;
42
+ return {
43
+ seq,
44
+ method: captured.method,
45
+ url: captured.url,
46
+ status: captured.response?.status,
47
+ hasResponseBody: captured.response?.body !== undefined,
48
+ };
49
+ });
50
+ this.save();
51
+ writePrivateFile(
52
+ join(this.toolDir, '.auth-live-discovery.json'),
53
+ JSON.stringify(
54
+ {
55
+ observedAt,
56
+ identities: Object.fromEntries(this.identities),
57
+ sessionPath: this.sessionPath,
58
+ note: 'Appended live browser observations are separate demonstrations; replay must obtain fresh state.',
59
+ },
60
+ null,
61
+ 2,
62
+ ),
63
+ );
64
+ return JSON.stringify({
65
+ observedAt,
66
+ page: observation.page,
67
+ capturedRequests: index,
68
+ captureErrors: observation.captureErrors.map((error) => ({
69
+ ...error,
70
+ seq: this.identities.get(`${observation.sourceId}:${error.seq}`),
71
+ })),
72
+ evidenceSessionPath: this.sessionPath,
73
+ guidance:
74
+ 'Read captured requests/responses with the recording tools and cite their seq as recordingRequestSeq. IMPRINT_SESSION_PATH now includes this evidence. Discovery is not compiled replay verification.',
75
+ });
76
+ }
77
+
78
+ private save(): void {
79
+ writePrivateFile(this.sessionPath, JSON.stringify(this.session));
80
+ }
81
+ }
@@ -0,0 +1,28 @@
1
+ /** Recording-grounded authentication plan shared by agent interfaces. */
2
+ import { z } from 'zod';
3
+
4
+ export const AuthCaptureSchema = z.object({
5
+ name: z.string().min(1),
6
+ /** Capture source: json | response_header | cookie | text_regex. */
7
+ source: z.string().min(1),
8
+ /** Path / header name / cookie name / regex that locates the value. */
9
+ locator: z.string().min(1),
10
+ /** Where the captured value is injected downstream, e.g. "header:Authorization". */
11
+ usedAs: z.string().default(''),
12
+ });
13
+
14
+ export const AuthToolPlanSchema = z
15
+ .object({
16
+ toolName: z.string(),
17
+ /** Explicit model-selected retained context; omitted preserves legacy label reuse. */
18
+ contextRef: z.string().min(1).optional(),
19
+ /** Deliberately isolated context; mutually exclusive with contextRef. */
20
+ newContext: z.boolean().optional(),
21
+ credentialRequestSeqs: z.array(z.number().int().nonnegative()).default([]),
22
+ authRequestSeqs: z.array(z.number().int().nonnegative()).default([]),
23
+ credentialNames: z.array(z.string()).default([]),
24
+ captures: z.array(AuthCaptureSchema).default([]),
25
+ notes: z.string().default(''),
26
+ })
27
+ .optional();
28
+ export type AuthToolPlan = z.infer<typeof AuthToolPlanSchema>;