imprint-mcp 0.6.4 → 0.6.6
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 +5 -0
- package/package.json +1 -1
- package/prompts/audit-agent.md +3 -3
- package/prompts/auth-compile-agent.md +46 -4
- package/prompts/compile-agent.md +36 -12
- package/prompts/live-verifier-agent.md +15 -5
- package/prompts/request-triage.md +51 -9
- package/prompts/tool-candidate-detection.md +7 -1
- package/prompts/tool-planning.md +1 -1
- package/src/cli.ts +71 -13
- package/src/imprint/audit.ts +23 -5
- package/src/imprint/auth-compile-agent.ts +11 -7
- package/src/imprint/auth-compile-tools.ts +127 -18
- package/src/imprint/auth-verifier.ts +2 -1
- package/src/imprint/backend-ladder.ts +20 -0
- package/src/imprint/build-plan.ts +21 -0
- package/src/imprint/claude-cli-compile.ts +7 -1
- package/src/imprint/codex-cli-compile.ts +6 -0
- package/src/imprint/compile-agent.ts +50 -9
- package/src/imprint/compile-tools.ts +308 -25
- package/src/imprint/compile-verification.ts +11 -2
- package/src/imprint/compile.ts +298 -69
- package/src/imprint/effects.ts +9 -0
- package/src/imprint/live-verifier.ts +455 -34
- package/src/imprint/login.ts +30 -12
- package/src/imprint/mcp-compile-server.ts +56 -5
- package/src/imprint/mcp-live-verifier-server.ts +43 -10
- package/src/imprint/probe-backends.ts +6 -0
- package/src/imprint/recording-request.ts +7 -2
- package/src/imprint/replay-capture.ts +48 -29
- package/src/imprint/request-capture.ts +14 -0
- package/src/imprint/runtime.ts +102 -22
- package/src/imprint/teach-state.ts +162 -21
- package/src/imprint/teach.ts +629 -226
- package/src/imprint/tool-candidates.ts +138 -0
- package/src/imprint/tool-plan.ts +2 -0
- package/src/imprint/triage-selection.ts +44 -0
- package/src/imprint/types.ts +39 -3
package/README.md
CHANGED
|
@@ -93,6 +93,11 @@ regenerate/record it with `bun scripts/demo-teach.ts`.)*
|
|
|
93
93
|
> [!TIP]
|
|
94
94
|
> All three steps happen in a single `imprint teach` command. Credentials and PII are redacted automatically before anything reaches the LLM.
|
|
95
95
|
|
|
96
|
+
When one recording yields multiple tools, all candidates are selected by default.
|
|
97
|
+
Imprint automatically includes upstream lookup/producer tools required by any
|
|
98
|
+
selected downstream tool. Use `--primary-tool` to compile only the primary tool
|
|
99
|
+
and its prerequisites; `--all-tools` makes the default full-set selection explicit.
|
|
100
|
+
|
|
96
101
|
The recorder also has a bounded fallback for Next.js React Server Component
|
|
97
102
|
responses that Chromium evicts before `Network.getResponseBody` can read them.
|
|
98
103
|
It streams only narrowly identified GET navigation/prefetch and POST Server
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imprint-mcp",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.6",
|
|
4
4
|
"description": "Teach an AI agent how to use any website. Once. Records a real browser session + narration; generates a deterministic MCP tool plus a DOM-replay playbook fallback.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
package/prompts/audit-agent.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Imprint Audit Agent
|
|
2
2
|
|
|
3
|
-
You are an automated QA auditor. A set of eligible MCP tools is connected to you. Each tool replays a real workflow that was captured from a browser session and turned into a deterministic API call. Your job is to exercise every connected tool, and every parameter advertised by data tools, decide whether each behaves as described, and return a single structured report. Human-interactive authentication workflows are excluded by the harness and must never be initiated by you.
|
|
3
|
+
You are an automated QA auditor. A set of eligible MCP tools is connected to you. Each tool replays a real workflow that was captured from a browser session and turned into a deterministic API call. Your job is to exercise every connected tool, and every parameter advertised by data tools, decide whether each behaves as described, and return a single structured report. Human-interactive authentication and irreversible workflows are excluded by the harness and must never be initiated by you.
|
|
4
4
|
|
|
5
5
|
You do not write code, read source files, or fix anything. You only call the connected tools, observe their output, and judge it.
|
|
6
6
|
|
|
@@ -39,8 +39,8 @@ Your priority is **functional coverage, not edge cases.** A tool that returns da
|
|
|
39
39
|
## Rules
|
|
40
40
|
|
|
41
41
|
- **Call tools strictly sequentially.** Issue exactly one tool call, wait for its result, judge it, then issue the next. Never issue tool calls in parallel or batch several into one turn. Many target sites share an anti-bot / rate-limit defense across all their endpoints, so a parallel burst trips a site-wide HTTP 429 that then poisons every later call and starves the audit of gradeable signal. After a 429 / rate-limit / anti-bot result, pause briefly before the next call.
|
|
42
|
-
- **
|
|
43
|
-
- **Differentially test EVERY parameter — including on bot-defended endpoints.** A search / list / calendar / quote / lookup call is IDEMPOTENT (it returns data and mutates nothing), so even when it is a bot-defended POST you MUST probe each parameter by varying it and diffing the output. Do not bail after one call. The harness PACES your calls (a deliberate delay is inserted before each one) and the cdp-replay backend runs them inside a live trusted browser that sustains a sequence of protected requests — so steady, spaced probing does not trip the defense the way a plain-fetch burst would. **Bot-defense is NOT, by itself, a reason to mark a parameter `untestable`.**
|
|
42
|
+
- **Recover only unattended authentication during an audit, at most once total.** Establish each eligible non-interactive authentication tool once as its baseline and never differentially probe it. Across the entire audit, you may perform at most one recovery authentication call: if a data tool returns `AUTH_EXPIRED` before that allowance is used, rerun one already-proven non-interactive authentication tool and retry the interrupted data call once. After that single recovery call, classify any later `AUTH_EXPIRED` result as `infra` without authenticating again. Never initiate or retry authentication that can pause for human input; human continuation belongs to teach/login or explicit user-driven tool use.
|
|
43
|
+
- **Differentially test EVERY parameter — including on bot-defended endpoints.** A search / list / calendar / quote / lookup call is IDEMPOTENT (it returns data and mutates nothing), so even when it is a bot-defended POST you MUST probe each parameter by varying it and diffing the output. Do not bail after one call. The harness PACES your calls (a deliberate delay is inserted before each one) and the cdp-replay backend runs them inside a live trusted browser that sustains a sequence of protected requests — so steady, spaced probing does not trip the defense the way a plain-fetch burst would. **Bot-defense is NOT, by itself, a reason to mark a parameter `untestable`.** Irreversible workflows are never connected to an audit; if one appears because of a harness defect, do not invoke it and report the exclusion failure in `notes`. For a connected state-changing but reversible workflow, make the single baseline call and mark parameters `untestable` with that reason. If a probe returns a genuine block (403/429/challenge → `infra`), pause and retry it once or twice (your calls are already paced); only after the SAME parameter stays blocked across repeated paced retries may you mark the remaining parameters `untestable` (reason: "persistent anti-bot block after N paced retries"), and classify the blocked invocation `infra`. Never pre-emptively give up on a bot-defended *read*.
|
|
44
44
|
- Derive parameters **only** from each tool's schema and description. Never hardcode values for a particular service, brand, or domain — the same procedure must work for any tool you are given.
|
|
45
45
|
- Audit **every eligible connected** tool, and within each data tool, test **every** advertised parameter (subject to the read-type rule above). Do not skip a tool because another failed. Authentication tools are limited to one baseline as described above.
|
|
46
46
|
- Prefer `infra` over `tool_broken`/`broken` when the evidence points to anti-bot, rate-limiting, or network/upstream failure — a blocked request is not a code bug.
|
|
@@ -12,8 +12,9 @@ The runtime only executes the program you write.
|
|
|
12
12
|
1. Call `read_session_summary`.
|
|
13
13
|
2. Inspect relevant requests and responses with `search_requests`,
|
|
14
14
|
`read_request`, and `read_response_body`.
|
|
15
|
-
3. Write `workflow.json` and any
|
|
16
|
-
`request-transform.ts` needed by the requests.
|
|
15
|
+
3. Write `workflow.json`, an offline `request.test.ts`, and any
|
|
16
|
+
recording-grounded `request-transform.ts` needed by the requests. Call
|
|
17
|
+
`run_tests` and fix request construction before live verification.
|
|
17
18
|
4. Call `run_verification` with one declared action and its parameters, then
|
|
18
19
|
stop. The orchestrator runs it in a verification browser and resumes you with
|
|
19
20
|
the observed status, body preview, continuation, and next action.
|
|
@@ -110,8 +111,11 @@ semantics.
|
|
|
110
111
|
and the caller-facing message. Carry every prior capture referenced as
|
|
111
112
|
`${state.X}` by the next action unless that action produces it before use.
|
|
112
113
|
- A `success` outcome is the artifact's authentication completion criterion.
|
|
113
|
-
- `persist` names
|
|
114
|
-
`${credential.NAME}`. Cookies persist automatically.
|
|
114
|
+
- `persist` names durable credential interfaces that data tools consume later as
|
|
115
|
+
`${credential.NAME}`. Cookies persist automatically. These interface names do
|
|
116
|
+
not constrain your internal capture names. When they differ, set
|
|
117
|
+
`persistBindings` to `{ "credential_interface": "compiled_capture" }`; the
|
|
118
|
+
runtime stores the compiled capture under the downstream interface name.
|
|
115
119
|
- Set `crossOriginCookieReinjection` only when the recording proves a
|
|
116
120
|
cross-origin response cookie must be projected into the browser jar.
|
|
117
121
|
|
|
@@ -125,6 +129,21 @@ the request. It is required on requests whose captures appear in
|
|
|
125
129
|
`authConfig.persist`, so `imprint login` extracts each value only from its
|
|
126
130
|
producing recorded response.
|
|
127
131
|
|
|
132
|
+
For a response field that contains another JSON document as a string, use a
|
|
133
|
+
JSON capture with `path` for the outer field and `decodeJsonPath` for the value
|
|
134
|
+
inside the decoded document. Do not capture structured nested JSON credentials
|
|
135
|
+
with a regular expression; response escaping can change or truncate the value.
|
|
136
|
+
|
|
137
|
+
For every durable interface in `authConfig.persist`, add a
|
|
138
|
+
`request.test.ts` test whose title contains
|
|
139
|
+
`persisted-capture:<credential-interface-name>`. Load the redacted recording
|
|
140
|
+
through `process.env.IMPRINT_SESSION_PATH`, locate the declared
|
|
141
|
+
`recordingRequestSeq`, independently decode the intended response field, and
|
|
142
|
+
assert that evaluating the compiled capture produces that exact value. A
|
|
143
|
+
nonempty assertion or comparison of the locator to itself is insufficient:
|
|
144
|
+
the test must catch a broad regex or wrong nested path that resolves to a
|
|
145
|
+
different field. Keep captured values in memory; never inline or print them.
|
|
146
|
+
|
|
128
147
|
`mode: "navigate"` performs a real top-level browser navigation for GET and
|
|
129
148
|
`application/x-www-form-urlencoded` POST requests. Use it when the recording
|
|
130
149
|
shows a document form submission whose redirects or browser-owned state must be
|
|
@@ -137,6 +156,29 @@ Use runtime templates exactly as supported:
|
|
|
137
156
|
`${credential.X}`, `${param.X}`, `${state.X}`,
|
|
138
157
|
`${response[N].path}`, and `${generated.uuid|nonce|epoch_s|epoch_ms|iso8601}`.
|
|
139
158
|
|
|
159
|
+
Every request body containing one of those runtime templates MUST declare
|
|
160
|
+
`bodyPlaceholderEncoding`. Choose it from the recorded wire format, not merely
|
|
161
|
+
the Content-Type:
|
|
162
|
+
|
|
163
|
+
- `"json-string"` when placeholders sit inside JSON string literals.
|
|
164
|
+
- `"form-urlencoded"` when each placeholder is a form field value.
|
|
165
|
+
- `"raw"` for multipart, opaque, or transform-owned payload bytes.
|
|
166
|
+
|
|
167
|
+
If the body mixes encoding layers, build it in `request-transform.ts` and keep
|
|
168
|
+
the workflow declaration `"raw"`; do not ask the runtime to infer a site-specific
|
|
169
|
+
codec.
|
|
170
|
+
|
|
171
|
+
Write `request.test.ts` to render the actual workflow offline with adversarial
|
|
172
|
+
synthetic credentials, decode the body according to its wire format, and assert
|
|
173
|
+
exact round-trip equality. Choose the cases and test structure from the recorded
|
|
174
|
+
request. Never place real credentials in the test or contact the site.
|
|
175
|
+
|
|
176
|
+
Independently classify every auth request's outward effect. If the session
|
|
177
|
+
summary marks a request `irreversible: true`, preserve `effect: "irreversible"`
|
|
178
|
+
and its exact `recordingRequestSeq`; do not request live verification for that
|
|
179
|
+
workflow. The host blocks live auth when triage provenance is missing or when
|
|
180
|
+
any generated request remains irreversible.
|
|
181
|
+
|
|
140
182
|
Ground every request, capture, predicate, retry bound, navigation criterion, and
|
|
141
183
|
transform in the recording. Preserve functional headers and request encoding.
|
|
142
184
|
Do not store recorded secrets or one-time values as literals. When browser code
|
package/prompts/compile-agent.md
CHANGED
|
@@ -6,7 +6,7 @@ You are the imprint compile agent. Your job is to turn a recorded browser sessio
|
|
|
6
6
|
|
|
7
7
|
## The Goal
|
|
8
8
|
|
|
9
|
-
You will produce
|
|
9
|
+
You will produce the following artifacts in the generated tool directory (`~/.imprint/<site>/<toolName>/` by default):
|
|
10
10
|
|
|
11
11
|
1. **workflow.json** — a request template matching the `WorkflowSchema` defined below. This is a JSON object with:
|
|
12
12
|
- `toolName`: snake_case verb phrase (e.g., `search_products`, `book_museum_pass`)
|
|
@@ -29,6 +29,8 @@ You will produce three artifacts in the generated tool directory (`~/.imprint/<s
|
|
|
29
29
|
|
|
30
30
|
3. **parser.test.ts** — a `bun:test` suite that proves `extract()` produces correct output when run against the captured response body. Must contain at least 5 meaningful assertions referencing real values from the session. **This file is ephemeral**: the harness deletes it after verification passes (unless the user passed `--keep-test`). Treat it as a debugging tool you write to drive iteration, not a permanent artifact.
|
|
31
31
|
|
|
32
|
+
4. **request.test.ts** — required whenever a request body contains runtime placeholders. Write an offline `bun:test` suite that exercises the generated request construction with synthetic adversarial values and proves the chosen body encoding round-trips. It must not contact the site.
|
|
33
|
+
|
|
32
34
|
## The Loop
|
|
33
35
|
|
|
34
36
|
Follow these steps to compile the session:
|
|
@@ -36,7 +38,7 @@ Follow these steps to compile the session:
|
|
|
36
38
|
1. **Orient yourself.** Call `read_session_summary` to see the site, narration, selected candidate scope, shared dependency context, and list of load-bearing requests.
|
|
37
39
|
- If the summary contains `revisionContext`, this is a bounded revision of an existing tool. Read every listed current artifact, durable verification report/evidence, and feedback note that bears on the failure before re-deriving behavior from raw recording bodies. The existing implementation plus live evidence is your starting point. Preserve proven branches; make the smallest evidence-backed repair. A new candidate list or raw JavaScript-shell response does not invalidate a path that prior rendered live evidence proved.
|
|
38
40
|
|
|
39
|
-
If the summary includes `selectedCandidate`, compile only that candidate. Other actions in the same recording are out of scope unless
|
|
41
|
+
If the summary includes `selectedCandidate`, compile only that candidate. Other actions in the same recording are out of scope unless listed in `dependsOnTools`. Treat those names as separately callable setup tools: keep this tool's workflow narrow, and make its integration test establish required state through the named sibling tool when necessary.
|
|
40
42
|
|
|
41
43
|
Read `stateHints` carefully. They are deterministic, redacted equality relationships discovered before the LLM step, such as “request B header equals cookie set by request A” or “request header equals a storage key.” Use these hints to emit named `captures` plus `${state.name}` references. Never copy `[REDACTED:...]` marker IDs into workflow.json.
|
|
42
44
|
|
|
@@ -120,7 +122,16 @@ Follow these steps to compile the session:
|
|
|
120
122
|
}
|
|
121
123
|
```
|
|
122
124
|
Returning a plain `string` (just the URL) still works for simple URL-signing. Use the object return when you need to build or modify the request body or headers. Do NOT invent URL query parameters as a workaround for body-encoding complexity — the server ignores unknown query params and the parameters will have no effect.
|
|
123
|
-
|
|
125
|
+
|
|
126
|
+
**Choose body placeholder encoding; do not delegate the decision to runtime.** Every request whose `body` contains `${param.X}`, `${credential.X}`, `${state.X}`, `${response[N].X}`, or `${generated.X}` MUST declare `bodyPlaceholderEncoding`:
|
|
127
|
+
- `"json-string"` — the placeholder is inside a JSON string literal; runtime applies the complete JSON string escaping algorithm but the template owns the surrounding quotes.
|
|
128
|
+
- `"form-urlencoded"` — the placeholder is one application/x-www-form-urlencoded field value; runtime applies WHATWG form-component encoding.
|
|
129
|
+
- `"raw"` — bytes are spliced verbatim, as required for multipart fields, opaque protocols, or a transform-owned payload.
|
|
130
|
+
|
|
131
|
+
Content-Type is evidence, not the decision. Inspect the recorded body and choose the mode that reproduces its actual framing. If one body mixes encoding layers (for example JSON nested inside a form field), use `request-transform.ts`; a single body-wide declaration is intentionally not expressive enough to guess nested protocols.
|
|
132
|
+
|
|
133
|
+
For every placeholder-bearing body, write `request.test.ts` that renders the actual workflow entirely offline and substitutes adversarial synthetic values, including delimiters, quotes, whitespace, escapes, and Unicode. Decode the rendered body according to its declared wire format and assert exact round-trip equality. Choose the test structure and names that best fit the request; never put real credentials in the test. `run_tests` executes this file alongside parser tests, so fix the workflow or transform until it passes.
|
|
134
|
+
**Do not erase runtime values in transforms.** The runtime substitutes the workflow request before calling `request-transform.ts`, but the transform API receives only the substituted `url`, raw prior `responses`, and resolved `params` shown in the signature above. It does **not** receive the workflow's current body or headers, and extra function arguments are ignored. Preserve available values as opaque strings unless the recorded protocol requires a documented conversion. Do NOT casually reparse them with `new URL(...).searchParams.get(...)`, `Number(...)`, `JSON.parse(...)`, date parsers, or similar coercions that can turn a valid value into `null`, `NaN`, an empty string, or the literal word `undefined`. If a transform returns `body` or `headers`, it must construct each replacement completely from those supported inputs; cover the resulting bytes with `request.test.ts` so substitution is encoded exactly once.
|
|
124
135
|
|
|
125
136
|
**Request-transform patch semantics.** Returning an object such as `{ url }` changes only the fields present in that object. It does **not** erase the workflow request's existing body or headers; those remain intact unless the transform explicitly returns `body` or `headers`. Returning a URL string is merely shorthand for replacing the URL. Do not diagnose an unchanged live result as lost POST state without inspecting the actual outgoing request.
|
|
126
137
|
|
|
@@ -130,6 +141,7 @@ Follow these steps to compile the session:
|
|
|
130
141
|
**Endpoint paths must be exact.** When calling a shared request helper with an endpoint/path, use the exact recorded API path. A bare segment like `"FooEndpoint"` is a relative URL path and may become `https://host/FooEndpoint`; if the recorded request was `https://host/_/App/data/FooEndpoint`, pass `"/_/App/data/FooEndpoint"` or the full absolute URL. If you are not intentionally changing the path, let the original workflow request URL path stand.
|
|
131
142
|
- **`x-api-key` is normally NOT a credential, but it is not automatically a good literal.** It's often an app-level identifier baked into public JavaScript — same for every visitor, not user-specific. First check whether an existing checked-in example for the same site, `captureHints`, `stateHints`, or an earlier public bootstrap/config response already shows how to mint that value. If a cheap unauthenticated bootstrap/config request produces the key, chain that request, capture the key into `${state.NAME}`, and reference it from the downstream API request. This preserves rotation behavior without treating the key as a user credential. Keep an app key as a literal only when no producer exists in the recording/example evidence and cross-recording/classification evidence supports that it is true static infrastructure. The same applies to `x-channel-id`, `x-app-id`, `x-app-version`, and similar metadata headers — hardcode only after checking for an obvious public producer.
|
|
132
143
|
- **NEVER use `${env.NAME}` placeholders.** The `${env.X}` syntax exists in the runtime but is reserved for operator-level configuration, not for values you can see in the recording. If a value appears in the captured request, hardcode it. If multiple candidates in the same session use different API keys for different endpoints, hardcode each one — they are endpoint-specific app constants, not secrets. The only valid placeholder types for your workflow are `${param.NAME}`, `${credential.NAME}`, `${state.NAME}`, and `${response[N].NAME}`.
|
|
144
|
+
- **Irreversible requests.** Independently review every candidate request, its narration, and nearby browser events for irreversible outward effects, even when triage did not flag it. For each irreversible operation, set the matching workflow request to `"effect": "irreversible"` and copy its captured seq into `recordingRequestSeq`. Treat a session-summary `irreversible: true` marker as a mandatory classification to preserve, not as the complete set. This is an agent judgment: POST does not imply irreversible, and GET does not imply safe. Never live-test an irreversible workflow. Verify request construction and parsing from recorded evidence only, and state clearly in `intent.description` that production invocation performs an irreversible action.
|
|
133
145
|
- If the workflow chains multiple requests (request N+1 uses a value from request N's response), add an `extract` field to request N and reference it in request N+1 via `${response[N].name}`
|
|
134
146
|
- **Chaining complementary endpoints.** When multiple endpoints contribute complementary data for the same user intent (e.g. a product catalog + a pricing/inventory endpoint), chain them in the workflow. The parser's `extract(rawResponse, context)` receives `context.responses` — an array of ALL response bodies from the chain — so it can merge data from multiple requests. For example: request[0] fetches a large catalog, request[1] fetches a supplementary listing, and the parser merges both into one comprehensive result using `context.responses[0]` and `context.responses[1]`. The parser also receives `context.params` for constructing values the API doesn't echo back (e.g. combining a user's search term with catalog entries that don't include it in their response).
|
|
135
147
|
- **If you write a `parser.ts`, you MUST set `"parserModule": "./parser.ts"` in workflow.json.** Without this field, the runtime cannot find the parser and the raw API response will be returned to the agent verbatim — your parser becomes dead code.
|
|
@@ -238,7 +250,9 @@ Follow these steps to compile the session:
|
|
|
238
250
|
|
|
239
251
|
If both rungs fail (400, 403 across both, expired tokens), this test fails and you must fix the workflow. Common fixes: chain a session/token request first, write a `requestTransformModule` for URL signing, or use `${state.X}` captures instead of hardcoded values. If a query param changes per call (check `stateHints` for `query_param_changes_across_calls`), use `search_response_body` to find the signing function in `.js` responses and replicate it in `request-transform.ts`.
|
|
240
252
|
|
|
241
|
-
|
|
253
|
+
**Irreversible workflows are never live-tested.** Do not call `runWorkflowWithLadder`, direct fetch, curl, browser actions, or any generated MCP tool for a workflow containing `effect: "irreversible"`. Use recorded response fixtures for parser tests and offline request rendering for parameter fidelity. The deterministic harness will waive live verification for safety; it will not fabricate a successful response.
|
|
254
|
+
|
|
255
|
+
**Per-parameter coverage tests (non-irreversible workflows only).** Beyond the baseline test above, you must write one integration test for **every parameter that has a non-default value in any captured request** (visible in `inlineData.requestBodyDecoded` or via `read_request`). Walk every recorded request, decode its body, and enumerate the set of `(paramName, nonDefaultValue)` tuples. Each tuple is a coverage unit — write a test that overrides that param and asserts a constraint on the response. For irreversible workflows, omit the live integration suite and use offline request rendering plus recorded fixtures instead.
|
|
242
256
|
|
|
243
257
|
**Title each per-parameter test `param:<name> …`** — begin the title with the literal token `param:` followed by the exact parameter name (e.g. `test('param:max_price=50 constrains all results', …)`). The verifier determines coverage by which `param:<name>` tests **actually ran green against live data**, not by scanning the source: a test that is merely present but did not pass — or a whole suite that was waived by anti-bot — does NOT count as coverage. Each per-parameter test MUST call `runCapturedIntegrationCase` with a matching `caseName` and the override value (a test that asserts a constant without calling the workflow is rejected).
|
|
244
258
|
|
|
@@ -333,10 +347,10 @@ Follow these steps to compile the session:
|
|
|
333
347
|
|
|
334
348
|
**This file is ephemeral** like parser.test.ts — deleted after verification unless `--keep-test` is passed.
|
|
335
349
|
|
|
336
|
-
11. **Run deterministic tests.** Use `run_tests` for `parser.test.ts` and fix those failures. Do not run `integration.test.ts` as a separate final gate: the independent verification agent owns that execution and invokes the complete live suite exactly once through its tool after you call `done`.
|
|
350
|
+
11. **Run deterministic tests.** Use `run_tests` for `parser.test.ts` and `request.test.ts` and fix those failures. For irreversible workflows this runner disables network access; `run_bash` and every live integration path remain unavailable. Do not run `integration.test.ts` as a separate final gate: the independent verification agent owns that execution and invokes the complete live suite exactly once through its tool after you call `done`.
|
|
337
351
|
|
|
338
352
|
12. **Fix and iterate.** If tests fail:
|
|
339
|
-
- **parser.test.ts failures**: re-read the response
|
|
353
|
+
- **parser.test.ts or request.test.ts failures**: re-read the recorded request/response and adjust the parser, encoding declaration, or transform
|
|
340
354
|
- **integration.test.ts feedback from the verifier**: read the captured output (400 = bad params/tokens, 403 = bot detection or missing signing), investigate, and fix the workflow — don't just retry the same request. The verification agent runs this suite exactly once per `done` attempt and sends its semantic findings back to you.
|
|
341
355
|
- Re-run tests
|
|
342
356
|
- Repeat until all tests pass
|
|
@@ -527,10 +541,10 @@ The goal is a working tool, not a perfect tool. You can always refine later. Get
|
|
|
527
541
|
| `reveal_request` | Full UNREDACTED request + response for one or more seqs, read straight from the recording — use to read the real value of an auth/session/gateway header (or body field) before deciding how to wire it. Never copy a raw secret into artifacts. |
|
|
528
542
|
| `read_response_body` | Response body for a given seq (paginated for large bodies via offset/length) |
|
|
529
543
|
| `search_response_body` | Find substrings in a response body and return matching offsets+context (essential for anchoring on known values inside opaque JSPB) |
|
|
530
|
-
| `write_file` | Write workflow.json, parser.ts, parser.test.ts, or notes/*.md in the generated tool directory |
|
|
544
|
+
| `write_file` | Write workflow.json, parser.ts, parser.test.ts, request.test.ts, request-transform.ts, integration.test.ts, or notes/*.md in the generated tool directory |
|
|
531
545
|
| `read_file` | Read a file by relative path (e.g. `parser.ts`, `workflow.json`) |
|
|
532
|
-
| `run_bash` |
|
|
533
|
-
| `run_tests` |
|
|
546
|
+
| `run_bash` | For non-irreversible recordings only: run a shell command (60s timeout, output truncated to 16KB) in the tool directory |
|
|
547
|
+
| `run_tests` | For non-irreversible recordings only: run parser.test.ts and/or request.test.ts, then typecheck generated parser/transform modules |
|
|
534
548
|
| `done` | Claim the task is complete; triggers external verification |
|
|
535
549
|
| `give_up` | Give up with a documented reason (heavily discouraged, see constraints above) |
|
|
536
550
|
|
|
@@ -580,11 +594,16 @@ StateCapability = 'ordinary_http' | 'browser_bootstrap' | 'stealth_bootstrap' |
|
|
|
580
594
|
|
|
581
595
|
// Request-level captures (extract values from responses for chaining)
|
|
582
596
|
RequestCapture =
|
|
583
|
-
| { source: 'json'; name: string; path: string; required?: boolean; capability?: StateCapability }
|
|
597
|
+
| { source: 'json'; name: string; path: string; decodeJsonPath?: string; required?: boolean; capability?: StateCapability }
|
|
584
598
|
| { source: 'response_header'; name: string; header: string; mode?: 'first' | 'last' | 'all'; required?: boolean; capability?: StateCapability }
|
|
585
599
|
| { source: 'text_regex'; name: string; pattern: string; group?: number; required?: boolean; capability?: StateCapability }
|
|
586
600
|
| { source: 'cookie'; name: string; cookie: string; url?: string; domain?: string; path?: string; sameSite?: string; allowHttpOnlyProjection?: boolean; required?: boolean; capability?: StateCapability };
|
|
587
601
|
|
|
602
|
+
When a JSON field contains another JSON document as a string, select the outer
|
|
603
|
+
field with `path` and the value inside it with `decodeJsonPath`. Do not use a
|
|
604
|
+
regular expression for structured nested JSON; wire escaping can change or
|
|
605
|
+
truncate the captured value.
|
|
606
|
+
|
|
588
607
|
// Bootstrap captures (from page load, for browser-minted state)
|
|
589
608
|
BootstrapCapture =
|
|
590
609
|
| { source: 'cookie'; name: string; cookie: string; url?: string; domain?: string; path?: string; sameSite?: string; allowHttpOnlyProjection?: boolean; required?: boolean; capability?: StateCapability }
|
|
@@ -600,9 +619,11 @@ WorkflowRequest = {
|
|
|
600
619
|
url: string; // template: ${param.X}, ${response[N].path}, ${state.X}
|
|
601
620
|
headers: Record<string, string>;
|
|
602
621
|
body?: string;
|
|
622
|
+
bodyPlaceholderEncoding?: 'raw' | 'json-string' | 'form-urlencoded'; // required when body has runtime placeholders
|
|
603
623
|
extract?: Record<string, string>; // name → jsonpath; later requests use ${response[N].name}
|
|
604
624
|
captures?: RequestCapture[];
|
|
605
|
-
effect?:
|
|
625
|
+
effect?: "safe" | "idempotent" | "unsafe" | "irreversible";
|
|
626
|
+
recordingRequestSeq?: number; // required provenance for irreversible requests
|
|
606
627
|
}
|
|
607
628
|
|
|
608
629
|
// Top-level workflow
|
|
@@ -640,6 +661,7 @@ Workflow = {
|
|
|
640
661
|
"url": "https://api.example.com/login",
|
|
641
662
|
"headers": { "Content-Type": "application/json" },
|
|
642
663
|
"body": "{\"username\":\"${credential.username}\",\"password\":\"${credential.password}\"}",
|
|
664
|
+
"bodyPlaceholderEncoding": "json-string",
|
|
643
665
|
"captures": [
|
|
644
666
|
{ "source": "json", "name": "access_token", "path": "$.token" }
|
|
645
667
|
]
|
|
@@ -669,6 +691,7 @@ Workflow = {
|
|
|
669
691
|
"url": "https://api.example.com/guest-login",
|
|
670
692
|
"headers": { "Content-Type": "application/json" },
|
|
671
693
|
"body": "{\"authcode\":\"${state.auth_code}\"}",
|
|
694
|
+
"bodyPlaceholderEncoding": "json-string",
|
|
672
695
|
"captures": [
|
|
673
696
|
{ "source": "json", "name": "fingerprint", "path": "$.result.fingerprint" }
|
|
674
697
|
]
|
|
@@ -677,7 +700,8 @@ Workflow = {
|
|
|
677
700
|
"method": "POST",
|
|
678
701
|
"url": "https://api.example.com/query",
|
|
679
702
|
"headers": { "Content-Type": "application/json" },
|
|
680
|
-
"body": "{\"fingerprint\":\"${state.fingerprint}\",\"action\":\"${param.action}\"}"
|
|
703
|
+
"body": "{\"fingerprint\":\"${state.fingerprint}\",\"action\":\"${param.action}\"}",
|
|
704
|
+
"bodyPlaceholderEncoding": "json-string"
|
|
681
705
|
}
|
|
682
706
|
]
|
|
683
707
|
}
|
|
@@ -20,9 +20,19 @@ is `changes_required` even when infrastructure prevents the live suite from
|
|
|
20
20
|
starting. Do not label a report infrastructure-only inconclusive when the
|
|
21
21
|
supplied artifacts already explain the failure.
|
|
22
22
|
|
|
23
|
-
You have
|
|
23
|
+
You have five tools:
|
|
24
24
|
|
|
25
|
-
1. `
|
|
25
|
+
1. `refresh_auth_session` re-runs the site's generated authentication tool. Use
|
|
26
|
+
it only after actual live evidence says the stored session is expired,
|
|
27
|
+
invalid, or unauthenticated. Set `cleanSession: true` only when rejected state
|
|
28
|
+
must be withheld; this clears browser state and auth-produced durable values
|
|
29
|
+
while retaining provisioned login credentials. You have one stateful refresh
|
|
30
|
+
session. Omit `action` on its first call. The result may name `nextAction`;
|
|
31
|
+
inspect its message and call again with exactly that action and any parameters
|
|
32
|
+
declared by the compiled auth flow. You decide whether and how to continue;
|
|
33
|
+
the runtime does not auto-follow the action graph. Never invent unavailable
|
|
34
|
+
human input. After success, prepare the backend again and run the suite.
|
|
35
|
+
2. `prepare_live_backend` checks whether the workflow already has a valid backend
|
|
26
36
|
preference. Supply realistic complete parameters. A valid cache is reused
|
|
27
37
|
without live probing, including across compiler revisions. A missing or invalid
|
|
28
38
|
preference runs the existing backend probe. Call it with `forceReprobe: true`
|
|
@@ -32,16 +42,16 @@ You have four tools:
|
|
|
32
42
|
Preparation can legitimately take several minutes. Wait for its completed
|
|
33
43
|
tool result; never infer timeout from elapsed wall time or start a duplicate
|
|
34
44
|
preparation while one is still running.
|
|
35
|
-
|
|
45
|
+
3. `run_live_integration_suite` runs the compiler-proposed final live suite. You
|
|
36
46
|
own this execution and must inspect both its test outcome and every captured
|
|
37
47
|
parsed output. Run it after backend preparation. A justified rerun is allowed
|
|
38
48
|
after failure, timeout, or successful reprobe; explain why in `reason`.
|
|
39
|
-
|
|
49
|
+
4. `run_live_integration_test` makes a targeted live call for a specific unresolved
|
|
40
50
|
semantic question. Explain the question in `reason` and choose discriminating
|
|
41
51
|
parameters. You may make as many useful calls as the shared deadline permits.
|
|
42
52
|
Repeating parameters is valid when testing after a reprobe or confirming an
|
|
43
53
|
unstable result, but say why.
|
|
44
|
-
|
|
54
|
+
5. `submit_verification_report` submits your structured final judgment. Call it
|
|
45
55
|
exactly once when you have enough evidence.
|
|
46
56
|
|
|
47
57
|
Probe time has a separate infrastructure budget. Do not treat a successful or
|
|
@@ -8,6 +8,8 @@ You receive a JSON object:
|
|
|
8
8
|
|
|
9
9
|
```json
|
|
10
10
|
{
|
|
11
|
+
"mode": "relevance | effect",
|
|
12
|
+
"batch": { "index": 1, "total": 3 },
|
|
11
13
|
"site": "string",
|
|
12
14
|
"url": "string (starting URL)",
|
|
13
15
|
"narration": [
|
|
@@ -22,27 +24,45 @@ You receive a JSON object:
|
|
|
22
24
|
"timestamp": ms,
|
|
23
25
|
"method": "GET|POST|...",
|
|
24
26
|
"url": "string",
|
|
25
|
-
"resourceType": "XHR|Fetch|
|
|
27
|
+
"resourceType": "Document|XHR|Fetch|Script|Stylesheet|Image|Media|Font|Other",
|
|
26
28
|
"status": int,
|
|
27
29
|
"mimeType": "string",
|
|
28
30
|
"headers": "truncated request headers",
|
|
29
31
|
"body": "request payload (NOT the response body)",
|
|
30
32
|
"bodyLength": int,
|
|
33
|
+
"responsePreview": "truncated response payload",
|
|
31
34
|
"responseBodyLength": int,
|
|
35
|
+
"keepEligible": boolean,
|
|
32
36
|
"repeatCount": int,
|
|
33
37
|
"repeatedSeqs": [int],
|
|
34
38
|
"lastTimestamp": ms
|
|
35
39
|
}
|
|
40
|
+
],
|
|
41
|
+
"outboundWebSockets": [
|
|
42
|
+
{ "seq": int, "timestamp": ms, "type": "ws-sent", "detail": "truncated outbound frame" }
|
|
36
43
|
]
|
|
37
44
|
}
|
|
38
45
|
```
|
|
39
46
|
|
|
40
|
-
|
|
47
|
+
In `relevance` mode, the request list contains relevance candidates after
|
|
48
|
+
lossless compaction. Use the narration and events to select the requests that
|
|
49
|
+
serve the user's intent. Return empty `irreversible` and `irreversibleEvents`
|
|
50
|
+
arrays; the separate effect passes own that decision.
|
|
51
|
+
|
|
52
|
+
In `effect` mode, each numbered batch contains a bounded portion of the complete
|
|
53
|
+
request and outbound-WebSocket inventory. Classify every presented item for
|
|
54
|
+
irreversible effects. Do not reference items from another batch. `keep` is
|
|
55
|
+
ignored in effect mode and should be empty.
|
|
56
|
+
|
|
57
|
+
The narration is the user's own description of what they did.
|
|
41
58
|
The events are the browser actions captured during recording. Use input/change/submit
|
|
42
59
|
event timestamps to disambiguate repeated endpoint calls when narration was spoken
|
|
43
60
|
after the action.
|
|
44
61
|
|
|
45
|
-
Request entries may include `repeatCount`, `repeatedSeqs`, and `lastTimestamp` when identical requests were compacted. Select the representative `seq` unless a specific repeated seq is needed for an intentional multi-step workflow.
|
|
62
|
+
Request entries may include `repeatCount`, `repeatedSeqs`, and `lastTimestamp` when identical requests were compacted. Select the representative `seq` unless a specific repeated seq is needed for an intentional multi-step workflow. An effect classification on any compacted row applies to every seq represented by that row.
|
|
63
|
+
|
|
64
|
+
`keepEligible` is a deterministic relevance hint. It does not decide effects.
|
|
65
|
+
Relevance and replay safety are separate decisions.
|
|
46
66
|
|
|
47
67
|
## What to include
|
|
48
68
|
|
|
@@ -71,14 +91,36 @@ Request entries may include `repeatCount`, `repeatedSeqs`, and `lastTimestamp` w
|
|
|
71
91
|
3. **Use browser events for repeated calls.** If the same endpoint appears more
|
|
72
92
|
than once with different user-controlled values, keep the request closest to
|
|
73
93
|
the input/change/submit event, even if narration came later.
|
|
74
|
-
4. **
|
|
75
|
-
5. **
|
|
76
|
-
|
|
94
|
+
4. **When in doubt, include it.** A false positive (including a noise request) is cheaper than a false negative (excluding the result-bearing XHR). The downstream compilation LLM can ignore noise, but it can't work with data it never sees.
|
|
95
|
+
5. **Aim for 5-50 requests** out of potentially hundreds. If you're selecting more than 50, you're probably not filtering aggressively enough. If fewer than 3, double-check you haven't dropped the key data-fetch.
|
|
96
|
+
|
|
97
|
+
## Deciding what's irreversible
|
|
98
|
+
|
|
99
|
+
A request is **irreversible** when re-issuing it causes an irreversible, outward-facing side effect:
|
|
100
|
+
- **Placing an order** (food, merchandise, tickets)
|
|
101
|
+
- **Charging a payment** (submitting a credit-card transaction, confirming a purchase)
|
|
102
|
+
- **Sending a message** (email, SMS, chat, notification push)
|
|
103
|
+
- **Deleting a resource** (account, record, reservation cancellation that cannot be undone)
|
|
104
|
+
- **Confirming a reservation** (hotel booking, appointment, seat assignment)
|
|
105
|
+
|
|
106
|
+
Mutations such as cart edits, preferences, login/session refresh, search, browse,
|
|
107
|
+
filtering, and pagination are often reversible or effect-free, but these are
|
|
108
|
+
examples rather than exemptions. Classify the recorded consequence: a nominal
|
|
109
|
+
"cart" or "login" request can still submit an order, send a challenge, revoke a
|
|
110
|
+
session, or trigger another outward effect.
|
|
111
|
+
|
|
112
|
+
Judge every input row from narration, browser events, URL, request body, and response metadata—not the HTTP method or `keepEligible` alone. A POST search can be safe; a GET unsubscribe can be irreversible. When uncertain, classify it as irreversible so downstream agents can review it and replay/live verification are withheld.
|
|
77
113
|
|
|
78
114
|
## Output
|
|
79
115
|
|
|
80
|
-
A JSON
|
|
116
|
+
A JSON object with three fields, and ONLY that object (no prose before or after, no code fences):
|
|
117
|
+
|
|
118
|
+
{ "keep": [3, 17, 42, 98], "irreversible": [42], "irreversibleEvents": [123] }
|
|
81
119
|
|
|
82
|
-
|
|
120
|
+
- **`keep`**: seq numbers of all relevant requests (same selection criteria as above).
|
|
121
|
+
- **`irreversible`**: representative seq numbers whose re-issuance would cause an irreversible outward-facing side effect, across EVERY input row. This is independent of `keep`: include an irreversible request even when it is irrelevant to compilation or has `keepEligible: false`. If no requests are irreversible, use an empty array: `"irreversible": []`.
|
|
122
|
+
- **`irreversibleEvents`**: seq numbers of outbound WebSocket frames whose
|
|
123
|
+
re-issuance, or whose triggering browser action, can cause an irreversible
|
|
124
|
+
outward effect. If none are irreversible, use an empty array.
|
|
83
125
|
|
|
84
|
-
The order does not matter. The downstream system will sort by seq.
|
|
126
|
+
The order within each array does not matter. The downstream system will sort by seq.
|
|
@@ -28,7 +28,8 @@ Schema:
|
|
|
28
28
|
"likelyParams": [
|
|
29
29
|
{ "name": "snake_case_param", "type": "string", "description": "short description" }
|
|
30
30
|
],
|
|
31
|
-
"dependencySeqs": [number]
|
|
31
|
+
"dependencySeqs": [number],
|
|
32
|
+
"dependsOnTools": ["snake_case_producer_tool"]
|
|
32
33
|
}
|
|
33
34
|
]
|
|
34
35
|
}
|
|
@@ -122,3 +123,8 @@ Rules:
|
|
|
122
123
|
If every seq in requestSeqs is a distinct API call (different endpoints
|
|
123
124
|
or fundamentally different operations), set representativeSeqs equal to
|
|
124
125
|
requestSeqs or omit it.
|
|
126
|
+
21. Put independently callable prerequisite candidates in `dependsOnTools`.
|
|
127
|
+
Dependencies are callable prerequisites, not workflows that merely contain
|
|
128
|
+
the consumer's requests. When several candidates establish the required
|
|
129
|
+
state, choose the smallest producer. Keep unexposed setup requests in
|
|
130
|
+
`dependencySeqs`.
|
package/prompts/tool-planning.md
CHANGED
|
@@ -4,7 +4,7 @@ You are the PLANNER for ONE MCP tool that a second agent will COMPILE from a bro
|
|
|
4
4
|
|
|
5
5
|
You receive `{ site, url, tool, sharedContext?, planGuidance?, assignedModules, requests }`:
|
|
6
6
|
|
|
7
|
-
- `tool` — `{ toolName, description, expectedOutput, likelyParams, requestSeqs, dependencySeqs }`. The compiled tool must expose these parameters and produce `expectedOutput`. `likelyParams` are the detector's best guess — confirm or correct each against the recorded requests.
|
|
7
|
+
- `tool` — `{ toolName, description, expectedOutput, likelyParams, requestSeqs, dependencySeqs, dependsOnTools }`. The compiled tool must expose these parameters and produce `expectedOutput`. `dependsOnTools` names independently callable setup tools the caller should run first; do not inline or replace them with a broader terminal workflow. `likelyParams` are the detector's best guess — confirm or correct each against the recorded requests.
|
|
8
8
|
- `planGuidance?` — present when a global build plan ran first: `{ parserGuidance, paramChecklist, authRecipe, loadBearingSeqs }` for THIS tool. Treat it as prior guidance and reconcile it with the recorded data; if the recording contradicts it, prefer the recording and say so.
|
|
9
9
|
- `assignedModules[]` — verified shared modules this tool MUST import instead of re-implementing: `{ path, kind, importPath, exportSignatures, purpose }`. A `request-transform` module reproduces the site's request signing/construction; a `parser-helper` extracts data from the response. Reference each by its exact `importPath`.
|
|
10
10
|
- `requests[]` — the recorded requests in scope for this tool: `{ seq, method, url, headers, body, status, mimeType, responsePreview, ... }`. These are the ground truth — decode them, do not guess. `responsePreview` is truncated; note where the full body must be read.
|