| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
native (#594) * Native Rust rewrite of agent-browser daemon Single-binary Rust implementation replacing the Node.js/Playwright daemon with direct CDP (Chrome DevTools Protocol) communication. Includes full command parity, WebDriver/Safari/iOS backend routing, request tracking, frame context management, CDP protocol codegen, and comprehensive tests. * improvements * fix ci * fixes * faster builds | 8 个月前 | |
Prefer snapshots over screenshots (#2453) * Prefer snapshots over screenshots in the core skill and chat prompt * Never screenshot to check for an element; wait and re-snapshot * Screenshot only when asked or for content the DOM cannot represent | 1 天前 | |
fix(daemon): keep security settings across daemon restarts (#2429) * fix(daemon): keep security settings across daemon restarts Allowed domains, action policy and confirm actions were lost with the daemon, so a restart silently dropped containment (#1894). They are now stored in an owner-only <session>.settings file before they apply, read back at daemon start, and removed by close. A file that cannot be used stops the daemon from starting. * docs: document persisted security settings * fix(daemon): harden stored security settings A CLI-driven daemon restart no longer clears the file, and close clears it only after the daemon confirmed the close, so a denied or parked close leaves the session contained. A relative action policy path is made absolute before it is stored. close --all also clears files of sessions whose daemon is gone. The boot refusal names the namespace and fits the stderr cap. The file is written with O_EXCL, read without following symlinks, and must belong to the daemon's user. * docs: describe when stored security settings are cleared * fix(daemon): close --all keeps settings of sessions that did not close close --all cleared the stored settings and reported success for a close that policy denied or that waited for confirmation. It now treats those as not closed. The legacy AGENT_BROWSER_POLICY path is stored absolute like the current variable, a ready daemon stores its security settings again so a racing sweep cannot leave it uncontained, and the boot hint quotes the session and namespace for the shell. * fix(cli): close keeps stored settings and policy paths stay valid UTF-8 close no longer deletes the stored settings before the daemon starts, so a policy that denies close still applies when the daemon was gone; the file is cleared only when the daemon refuses to start because of it. A policy path that is not valid UTF-8 after resolving is rejected instead of being stored lossy, and the path test no longer assumes forward slashes. * fix(cli): close --all closes sessions without a daemon under their stored policy close --all deleted the settings file of every session with no running daemon, so a policy that denies close was bypassed. Those sessions are now closed through the same path as close: a daemon starts with the stored settings, a denied or parked close keeps the file and is reported as failed, and a file the daemon refuses is cleared. * fix(cli): name the socket dir in the refusal hint and stop orphan daemons a failed close --all started * fix(cli): close clears an empty directory in place of the settings file and names an entry it cannot remove * test(cli): close --all leaves no daemon for a dead session it could not close * fix(cli): sync the settings directory after the rename and give the two-step way to confirm a parked close * fix(daemon): restore a stored empty allowed-domains list as no filter * test(daemon): a separator-only allowed-domains list leaves no filter | 2 天前 | |
feat(flow): record a browser task once and replay its requests (#2236) * feat(flow): add saved flow recording, storage and HTTP replay Records the commands of a task with the first-party JSON requests each one fired, stores them without credentials, and replays the output request directly with a response shape check. * feat(flow): add flow start, save, run and list commands The daemon tags HAR entries with the running command and announces saved flows for a site after navigation. A failed run marks the flow unverified only when the site changed, not when params were rejected. * feat(flow): print flow results and the saved flows hint The hint shows recorded param values, the recorded page and item field names so an agent can call a flow without guessing its inputs. * feat(mcp): expose saved flows as core tools Adds flow list, run, start and save to the core profile, carries the saved flows hint in navigation results, and formats flow results as text. * docs(skills): document saved flows in the core skill Covers when to prefer flow run over driving the page, how to record a flow, and points derive-client at it for tasks the agent repeats. * fix(network): let mocked cross-origin responses pass CORS A route with --body answered cross-origin requests without CORS headers and with no content type, so the page saw a network error and HAR capture dropped the body. Mocked responses now echo the request origin, answer preflights, and label JSON bodies as application/json. * feat(flow): fall back to the page and to a command replay A run now tries direct HTTP, then the same request from the page, then a replay of the recorded commands, and starts the next run at the tier that worked. Every tier checks the recorded response shape; a change marks the flow unverified and is reported as drift. Session routes answer the HTTP tier too, so mocks reach every tier. A write the site accepted is never sent again by a lower tier, and a replay refuses params it cannot apply. * feat(flow): find params and references when saving A value a request sent that came from an item of an earlier list becomes a param with its source; a value copied from a single field is filled from that response on each run, and the requests it depends on run first. A login form on the page at save time names the last gap. The saved flows hint says where each detected param comes from. * feat(webmcp): list saved flows next to declared tools webmcp list now shows the saved flows for the page's site under an observed section, so declared and observed tools can be compared. * fix(flow): keep each GraphQL request and treat queries as reads Requests were deduplicated by method and URL, so a GraphQL app kept only its first POST. The body is now part of the key, and a POST that only carries GraphQL queries no longer needs --allow-write; mutations still do. * feat(flow): record a flow on an already open page flow start keeps the tab's URL, so a task that begins on a logged-in page can be saved without an open command, and a replay starts from that page. * fix(flow): replay recorded commands on live apps Snapshot refs are gone by the next run, so commands that used one are saved as the role and name it pointed at. Replayed commands wait for an element the page is still rendering, and a captured request matches a recorded one by its body or GraphQL operation, since one endpoint serves every query. * fix(flow): send writes the way a browser does The HTTP tier now sends Origin on writes, taken from the recorded referer, since sites check it before accepting one. A write the site did not accept may carry stale page tokens, so it moves on to the page and the replay tiers instead of stopping. * fix(flow): chain only generated values from read responses Short words and locale codes repeat across a site's responses, so they turned into long chains of references, some through POST requests. A reference now needs a value that looks generated, comes only from a read, and replaces whole tokens, never part of a longer word. * feat(flow): take params from the start URL and find nested lists A query value of the recorded page URL that the API requests also sent, such as ?keyword=, becomes a param without --param. --where, --fields and the hint's item fields now use the heaviest list anywhere in the response, since GraphQL nests it under data. * feat(flow): read JSON embedded in server-rendered pages Sites that render on the server send no API request for their main data, but often embed it in the HTML as a JSON script, a Next.js __NEXT_DATA__ block or a window.x = {...} assignment. flow save now keeps those blocks from first-party documents as requests that fetch the page and read the same block back, so they get params, shape checks and --fields like any API response. The page's own path segments and blocks of the same document are no longer taken as params or references. * fix(flow): treat a response without its lists as no results Many APIs drop an empty list and its counts instead of sending []. That used to read as a changed shape and mark the flow unverified. When only lists and counts are missing and no new list appeared, the run now returns no items and leaves the verified state as it was. * docs: document saved flows Adds a saved flows section to the network page and two lines to the README command list: recording, params, the run tiers, discovery and storage. * feat(flow): describe saved flows in the shape of WebMCP tools Saved flows now carry a description and an inputSchema built from their params, returned by flow list and shown by webmcp list after the tools the page declares. When a page declares WebMCP tools, they come before the saved flows hint, and the hint suggests them first. flow run still runs flows; webmcp invoke only calls the page's own tools. * fix(flow): close the gaps found in security review - Every resolved request URL, and every redirect the HTTP tier follows, must pass the session's current domain allowlist. - A command replay sends every write the recording caused, so it needs --allow-write whenever the flow recorded one, not only for writes the output depends on. - Param values are substituted into parsed JSON strings, so quotes and backslashes keep request bodies and replayed commands valid. - Suggested commands shell-quote recorded param values, and inferred param names keep only letters, digits and underscores. - Values typed into password-like fields are not stored, auth and credential commands are not recorded, and flow files and directories are created owner-only. - webmcp list prints each saved flow's inputs as name, type and recorded value. * fix(flow): keep typed secrets out of saved commands and requests type stores its input in text, which the redaction skipped. Both fields are now redacted, and the typed value is kept in memory during the recording so that flow save drops any captured request whose URL, body or headers carry it, and reports why. * feat(flow): capture flows with --capture flow and save several per session Recording moves from `flow start` to a launch flag. Every recorded command returns recorded.seq, failed commands stay in the recording as ok:false, and `flow steps` lists them. `flow save <name>` takes --from/--to/--description, no longer resets the recorder, and close reports unsaved steps. The hint hides only flows saved in the same session, and har stop is refused while capture owns the recorder. Flow files are version 2; older files must be saved again. * fix(flow): keep runs on the flow's own site A flow records the base domain of its start URL as allowedHosts. A run rejects any request, redirect or recorded navigation outside it, whatever --allowed-domains says, and the in-page tier only runs on a tab that is already on that site. Flow origins are validated as hostnames before they become directory names, and IP hosts no longer collapse to a shared base. * feat(flow): list writes at save and confirm each one before a run flow save records writes[{id, method, url, kind}] with kind explicit or suspected. A GET is suspected when a click fired it and no output depends on it, or when its path has a whole logout, signout, unsubscribe, delete or remove segment. --allow-write takes all or write ids, in the CLI and in flow_run. A write it does not cover asks for confirmation as policy action flow_write:<id>, so --action-policy and --confirm-actions reach the http and page tiers; the command replay checks the writes no request covers. * feat(flow): redact secret fields by what the page says and keep typed values as params Recording reads type, autocomplete and the field's name, id, label and placeholder from the element behind a fill or type, so an unlabeled one-time-code or cc-* input is redacted. Any other typed value becomes an auto-named param with source typed, stored as a placeholder in the step, or the --param name when one has the same value. Params record their source (typed, query, response) and the file lists redactedSteps. * feat(flow): stop a run on auth, rate limit and challenge responses A 401 or 403 returns auth_expired, a 429 returns rate_limited with retryAfter when the site sent one, and a bot challenge page returns blocked. Each stops the chain instead of falling to a lower tier, carries a machine-readable code and leaves the flow's verified state alone. * fix(flow): ask only about the writes a run sends A run now gates the writes among the requests its output needs, not every write the recording caused; the command replay checks the rest. Adds a Chrome e2e against a local fixture: sign-in cookie, CSRF write through a response value, GraphQL query, a GET side effect, a cross-domain redirect, and two saves in one session. commands.mdx stays as it was because the docs route fixtures freeze it. * fix(flow): close review findings on credentials, redirects, replay and typed values - A 401 or 403 on a request recorded with stripped credential headers falls through to the page and replay tiers; auth_expired is reported when every way is refused. - A blocked redirect returns its 3xx, a write with a 2xx or 3xx answer is accepted, a write with no answer returns outcome_unknown and is never resent, and the in-page write does not follow redirects. - The replay runs only plain interactions, checks every URL in a recorded command, and stops when the tab leaves the flow's site. - keyboard type and inserttext read the focused element; typed params keep no example. - base_domain uses the public suffix list; a flow whose origin directory does not match its start URL must be saved again. - A save of an earlier range no longer moves the save marker back, and challenge pages are recognized only in HTML. * fix(flow): keep vault and key-by-key passwords out of flows and close URL and retry gaps - Replay URL checks read every string the way the URL parser does, so tabs, newlines and leading control characters cannot hide a host, and URL keys are checked whatever the value looks like. - The in-page fetch sends a write once; a thrown fetch is outcome_unknown. - A command that is not recorded tags no requests to the previous step, and the password auth login fills is registered as a secret. - Keys pressed in a secret field are gathered into one secret, redacted from the steps, and requests carrying it are dropped at save. * fix(flow): keep modifier chords and clipboard text out of secret handling gaps A key joins a secret only when it is bare or Shift plus a key, so Control+a no longer corrupts the buffer. A paste chord or clipboard paste into a secret field redacts the step and drops the requests it tagged, and clipboard write text is redacted and registered as a secret. * fix(flow): register a pasted password once the paste has finished After a paste chord or clipboard paste into a secret field, the focused field's value is read and registered as a secret in full, with any typed prefix, and later keys extend it. A request a later step sends with it is no longer saved. When the value cannot be read, the requests tagged to the paste step are still dropped. * fix(flow): replay once per run, recognize encoded secrets and link writes to earlier responses - The command replay runs at most once per run; every request that falls back to it reads its answer from that replay, and it is skipped when another tier already sent a write it would repeat. - Requests carrying a secret are recognized in form-encoded, percent-encoded and JSON-escaped forms. - A write's request takes a value from an earlier response and depends on it, so a rotating token is read fresh at each run. * fix(flow): never resend a replayed write, keep rejected writes eligible, match replays by query - Before any tier sends a write, the run checks whether it was already sent. A write the replay sent is answered from the replay's capture, and stops with outcome_unknown when the capture has no answer. - Only a 2xx or 3xx answer, or an unknown outcome, counts as a sent write; a 4xx rejection leaves the replay available. - Replayed requests match by method, host, path, recorded query and body, ignoring query parameters the recording never had; a missing or ambiguous match fails for that request. JSON bodies compare by value. * fix(flow): read the flow once per run and carry it through confirmation A flow_run loads its file once. Policy, the write gate and the run use that copy, and a confirmation keeps it so the confirmed run executes the file that was checked, not one read again from disk. The write gate is one function used before the run and inside it. * refactor(flow): one secret-field predicate and one header classifier FieldTraits::looks_secret replaces four copies of the type, autocomplete and name check. HeaderClass names the four ways a recorded header is treated, in place of three separate lists. * refactor(flow): type the saved flow and move to a module directory Flow, Step, Request, Write, Param and Tier are serde types with unknown fields rejected, so the file schema is checked on load and a run no longer walks serde_json::Value. Request.write replaces the writes lookups in the run path. The file format is version 1; nothing older has shipped, so the old-version path is gone. Fixtures build flows through serde_json. * refactor(flow): one write ledger and next_tier decide when a request is sent again WriteLedger records which writes a run sent, and next_tier picks the next tier from the attempts so far. They replace the accepted, retry, completed and already-sent checks that were spread over the run loop, the replay and its guard. A write that was sent is only read back from the replay's capture. * refactor(flow): a Sender per tier behind ports, and the run loop in flow HttpSender, PageSender and ReplaySender each live in their own file and implement Sender. The browser session is reached through the Host and CommandRunner ports, which actions.rs implements in one wiring struct, so the replay no longer lives in actions.rs. run_requests is the run loop; an in-memory Senders drives it in tests without a browser or a network. The navigation hint moves into flow. * refactor(flow): the recorder keeps its fields and offers begin, tag, end, steps and save actions.rs no longer reads or writes recorder internals. It enters and exits a command, begins and ends it with what the page said, and asks for the steps or a save. Range resolution, marking a save and the session's saved list live behind save. Step stabilizing moves into the recorder. * refactor(flow): one host policy, one failure constructor, no pass-throughs HostPolicy is the only place a URL is checked against a flow's hosts; the recording's first-party test, the run, the redirect policy, the replay and the page tier all use it. TierResult::fail with Failure::drift, unavailable and unknown_outcome replaces five result builders. replay_match, validate_name's wrapper, write_policy_action and flow_for_command are gone; a Write names its own policy action. * refactor(flow): drop the click-fired write guess and the --from keywords A GET is a suspected write only when its path has a whole logout, signout, unsubscribe, delete or remove segment; guessing from the command that fired it needed the output's dependencies and the request's resource type. --from takes a step number, and without it a save starts at the last navigation after the previous save. Help, MCP, the tool catalog, docs and the skill reference follow. * test(flow): table-driven cases, behavior names, and key tests through the save Hosts, check_response, GraphQL and secret-field cases are tables. The tests named after review comments carry the behavior they check. Key-buffer, paste and clipboard tests ask whether a request carrying the secret survives a save instead of reading the recorder's secrets. * fix(flow): a request is a write only when the writes list says so Request.write was a second copy of what Flow.writes records, and it defaulted to false, so a file could disagree with itself and a write could skip the ledger. The run reads the writes list alone. * refactor(flow): build the flow from its types and put write queries on Flow build_flow and the reference linker create Step, Request and Param directly instead of json! values that were parsed back into the same types. run_writes, replay_only_writes and AllowWrite::check replace four free functions that walked the writes list. * refactor(flow): three tier functions over one Browser port http::send, page::send and replay::send replace the Sender trait and its three structs. Host and CommandRunner become one Browser trait. The Senders seam and its in-memory adapter stay. * refactor(flow): Stop and RunOutcome name why a run ended, and a flow is read once Stop replaces the four string codes that were matched in send, memory and actions. RunOutcome builds the stopped, drift and failed messages with unit tests, so handle_flow_run no longer formats them. The run's host check is the per-request one after references are filled. A flow_run that cannot read its flow fails at the first read, before policy, so policy and the run never see two different files. * fix(flow): tag a request by when it started and wait on conditions in the e2e A request belongs to the command that was running when it started, using the request's own start time against each command's begin time, so a late event read during the next command keeps its step. The replay's wait no longer stalls for its full timeout: a request body that still has references to fill is matched on the request alone. The e2e wait for the server to see a click's request and for the browser to finish reading it, instead of sleeping. * fix(flow): check every request's host before the first one is sent A flow with a foreign host in a later request sent the earlier requests before rejecting it. The run now checks every request URL against the flow's hosts up front, on the template with references unfilled, and again per request once they are filled. * fix(flow): a flow that sends a write it does not list fails to load A request that changes state and is missing from the writes list would run with no gate and no once-only guarantee, so an edited file could drop it. Loading now refuses the file and asks to save it again. * fix(flow): the replay lets each command's requests finish before the next A command that depends on what the previous one fetched, such as a click that sends a token the page stored from the last response, raced the response and sent the wrong body. * refactor(flow): ship flow run over http only Flow run sends the saved requests over direct HTTP. The in-page fetch and command replay tiers, the cross-tier write ledger, recorded commands in the file and the tier preference are removed; a 401 or 403 is auth_expired. Every HTTP guard stays: host policy, write gate, single load, shape verification, redaction and owner-only files. Docs, help, skill reference and the MCP catalog describe the HTTP path only. * fix(flow): report a stripped credential instead of an expired session A request recorded with an authorization, token or CSRF header is marked strippedAuth at save. A 401 or 403 for it stops the run with credential_not_sendable, since signing in again cannot supply what the file does not keep. Other 401 and 403 responses stay auth_expired. The file no longer lists redactedSteps: it holds no steps to point to. * fix(flow): keep params out of GraphQL operations and expand placeholders once A param fills the variables and other fields of a GraphQL body, never the operation text, and an operation with a placeholder in its text counts as a write. After values are filled, a request that classifies as a write but is not in writes is refused before anything is sent, for params and for references. Params and references are expanded in one pass over the template, so a response or param value that contains placeholder text is inserted as data instead of being expanded again, which looped without end. * fix(flow): refuse a read whose filled path names a state change After params and references are filled, a GET whose path has a whole logout, signout, unsubscribe, delete or remove segment and is not listed in writes is refused before it is sent, the same as a request that becomes a write by method or GraphQL operation. * fix(flow): encode values per slot, gate every write, never resend a write on redirect Placeholders are parsed once into parts and bound in a single pass, with each value encoded for where it lands: a URL query or path segment, a form body, or a JSON string. Text that was inserted is never read as a placeholder again. At save, a typed value is found in the form its slot writes it, so quotes, backslashes, newlines, ampersands and plus signs in a JSON or form body become params instead of staying literal. A flow with no output sends every request, and every write among them goes through the write gate. An empty dependency no longer replaces a nonempty output with an empty result. A write never follows a redirect, including a 307 or 308; the 3xx is its answer. * fix(flow): read a form by its shape and refuse values that break a raw body A body shaped k=v&k=v is a form whatever its content type, so a param value with & or = stays inside its field. For a header, a plain text body or a multipart body, a value with a line break, or one that contains the multipart boundary, is refused before anything is sent. * fix(flow): keep path segments shared across requests literal when linking response values A value found in an earlier response replaced a path segment such as /api when the response held the same word (gitRootDirectory = "api"). A segment present in the path of more than one recorded request is site structure, so it is no longer linked to a response. * fix(flow): accept capture request ids for --output and list valid ids on error Saved ids are renumbered within the saved range, while the ids a capture shows keep their step sequence. --output now accepts either form, and an unknown id lists the ids that are valid. * fix(flow): drop per-request tracing and client-identity headers when saving x-client-ip, x-forwarded-for, x-real-ip, forwarded, traceparent, tracestate, x-request-id, request-id, x-amzn-trace-id, sentry-trace, baggage, cf-connecting-ip and true-client-ip identify the machine or one request. Replay sends only the saved headers and reads none of these, so they are classified as dropped next to cookie and host. * fix(flow): do not promote a page-query value that only the Referer carries A start-URL query value counts as sent when any recorded entry, saved or not, contains it, so `?create=true` became a param although only the Referer of the saved requests held it. After the requests are templated, a page-query param used by no URL, body or other header is dropped and the Referer keeps its recorded value. * fix(flow): decide structural values by source instead of how many paths share them A response value is left literal when it is already a path segment of the request it came from or of an earlier one. Counting requests also stopped ids that two later paths use, such as a project id, from becoming params or references. * style(flow): remove the empty line after a doc comment * fix(flow): refuse an --output id that names two different requests When a range starts after step 1, a capture id and a saved id can be the same string for different requests. The error names both meanings. The accepted id forms are documented. * fix(flow): template the restored Referer against the params that remain Restoring the raw Referer when a page-query param is dropped also removed the placeholders of params that are kept. The recorded Referer is now templated against the remaining params. * fix(flow): report drift and signed requests, name dropped secret requests, link ids to their source - A recorded list missing from the response is drifted; a present empty list stays ok. The main list path is saved and checked, including lists deeper than the shape signature. - A 401 or 403 on a request carrying a per-request signature or timestamp reports request_signed; static or word-like values keep auth_expired with a hint naming the header. - A request dropped for carrying a secret-field value is named by method and path with the secret redacted, or by host when the value cannot be located. - Response values link to the latest supplying response that does not echo the value from its own path, preferring lists, so a param chosen from a list carries through detail and nested requests. * fix(flow): keep the recorded list's items when only counts are missing A response that omits a recorded count but still has its list is ok and returns the items; it is empty only when the list itself is empty. * fix(flow): reword capture guidance for the relaunch behavior and validate raw launch capture The launch body's capture value is checked like the per-command setting, the not-capturing message and --capture help describe the relaunch, and the capture e2e asserts it. | 3 天前 | |
feat(flow): record a browser task once and replay its requests (#2236) * feat(flow): add saved flow recording, storage and HTTP replay Records the commands of a task with the first-party JSON requests each one fired, stores them without credentials, and replays the output request directly with a response shape check. * feat(flow): add flow start, save, run and list commands The daemon tags HAR entries with the running command and announces saved flows for a site after navigation. A failed run marks the flow unverified only when the site changed, not when params were rejected. * feat(flow): print flow results and the saved flows hint The hint shows recorded param values, the recorded page and item field names so an agent can call a flow without guessing its inputs. * feat(mcp): expose saved flows as core tools Adds flow list, run, start and save to the core profile, carries the saved flows hint in navigation results, and formats flow results as text. * docs(skills): document saved flows in the core skill Covers when to prefer flow run over driving the page, how to record a flow, and points derive-client at it for tasks the agent repeats. * fix(network): let mocked cross-origin responses pass CORS A route with --body answered cross-origin requests without CORS headers and with no content type, so the page saw a network error and HAR capture dropped the body. Mocked responses now echo the request origin, answer preflights, and label JSON bodies as application/json. * feat(flow): fall back to the page and to a command replay A run now tries direct HTTP, then the same request from the page, then a replay of the recorded commands, and starts the next run at the tier that worked. Every tier checks the recorded response shape; a change marks the flow unverified and is reported as drift. Session routes answer the HTTP tier too, so mocks reach every tier. A write the site accepted is never sent again by a lower tier, and a replay refuses params it cannot apply. * feat(flow): find params and references when saving A value a request sent that came from an item of an earlier list becomes a param with its source; a value copied from a single field is filled from that response on each run, and the requests it depends on run first. A login form on the page at save time names the last gap. The saved flows hint says where each detected param comes from. * feat(webmcp): list saved flows next to declared tools webmcp list now shows the saved flows for the page's site under an observed section, so declared and observed tools can be compared. * fix(flow): keep each GraphQL request and treat queries as reads Requests were deduplicated by method and URL, so a GraphQL app kept only its first POST. The body is now part of the key, and a POST that only carries GraphQL queries no longer needs --allow-write; mutations still do. * feat(flow): record a flow on an already open page flow start keeps the tab's URL, so a task that begins on a logged-in page can be saved without an open command, and a replay starts from that page. * fix(flow): replay recorded commands on live apps Snapshot refs are gone by the next run, so commands that used one are saved as the role and name it pointed at. Replayed commands wait for an element the page is still rendering, and a captured request matches a recorded one by its body or GraphQL operation, since one endpoint serves every query. * fix(flow): send writes the way a browser does The HTTP tier now sends Origin on writes, taken from the recorded referer, since sites check it before accepting one. A write the site did not accept may carry stale page tokens, so it moves on to the page and the replay tiers instead of stopping. * fix(flow): chain only generated values from read responses Short words and locale codes repeat across a site's responses, so they turned into long chains of references, some through POST requests. A reference now needs a value that looks generated, comes only from a read, and replaces whole tokens, never part of a longer word. * feat(flow): take params from the start URL and find nested lists A query value of the recorded page URL that the API requests also sent, such as ?keyword=, becomes a param without --param. --where, --fields and the hint's item fields now use the heaviest list anywhere in the response, since GraphQL nests it under data. * feat(flow): read JSON embedded in server-rendered pages Sites that render on the server send no API request for their main data, but often embed it in the HTML as a JSON script, a Next.js __NEXT_DATA__ block or a window.x = {...} assignment. flow save now keeps those blocks from first-party documents as requests that fetch the page and read the same block back, so they get params, shape checks and --fields like any API response. The page's own path segments and blocks of the same document are no longer taken as params or references. * fix(flow): treat a response without its lists as no results Many APIs drop an empty list and its counts instead of sending []. That used to read as a changed shape and mark the flow unverified. When only lists and counts are missing and no new list appeared, the run now returns no items and leaves the verified state as it was. * docs: document saved flows Adds a saved flows section to the network page and two lines to the README command list: recording, params, the run tiers, discovery and storage. * feat(flow): describe saved flows in the shape of WebMCP tools Saved flows now carry a description and an inputSchema built from their params, returned by flow list and shown by webmcp list after the tools the page declares. When a page declares WebMCP tools, they come before the saved flows hint, and the hint suggests them first. flow run still runs flows; webmcp invoke only calls the page's own tools. * fix(flow): close the gaps found in security review - Every resolved request URL, and every redirect the HTTP tier follows, must pass the session's current domain allowlist. - A command replay sends every write the recording caused, so it needs --allow-write whenever the flow recorded one, not only for writes the output depends on. - Param values are substituted into parsed JSON strings, so quotes and backslashes keep request bodies and replayed commands valid. - Suggested commands shell-quote recorded param values, and inferred param names keep only letters, digits and underscores. - Values typed into password-like fields are not stored, auth and credential commands are not recorded, and flow files and directories are created owner-only. - webmcp list prints each saved flow's inputs as name, type and recorded value. * fix(flow): keep typed secrets out of saved commands and requests type stores its input in text, which the redaction skipped. Both fields are now redacted, and the typed value is kept in memory during the recording so that flow save drops any captured request whose URL, body or headers carry it, and reports why. * feat(flow): capture flows with --capture flow and save several per session Recording moves from `flow start` to a launch flag. Every recorded command returns recorded.seq, failed commands stay in the recording as ok:false, and `flow steps` lists them. `flow save <name>` takes --from/--to/--description, no longer resets the recorder, and close reports unsaved steps. The hint hides only flows saved in the same session, and har stop is refused while capture owns the recorder. Flow files are version 2; older files must be saved again. * fix(flow): keep runs on the flow's own site A flow records the base domain of its start URL as allowedHosts. A run rejects any request, redirect or recorded navigation outside it, whatever --allowed-domains says, and the in-page tier only runs on a tab that is already on that site. Flow origins are validated as hostnames before they become directory names, and IP hosts no longer collapse to a shared base. * feat(flow): list writes at save and confirm each one before a run flow save records writes[{id, method, url, kind}] with kind explicit or suspected. A GET is suspected when a click fired it and no output depends on it, or when its path has a whole logout, signout, unsubscribe, delete or remove segment. --allow-write takes all or write ids, in the CLI and in flow_run. A write it does not cover asks for confirmation as policy action flow_write:<id>, so --action-policy and --confirm-actions reach the http and page tiers; the command replay checks the writes no request covers. * feat(flow): redact secret fields by what the page says and keep typed values as params Recording reads type, autocomplete and the field's name, id, label and placeholder from the element behind a fill or type, so an unlabeled one-time-code or cc-* input is redacted. Any other typed value becomes an auto-named param with source typed, stored as a placeholder in the step, or the --param name when one has the same value. Params record their source (typed, query, response) and the file lists redactedSteps. * feat(flow): stop a run on auth, rate limit and challenge responses A 401 or 403 returns auth_expired, a 429 returns rate_limited with retryAfter when the site sent one, and a bot challenge page returns blocked. Each stops the chain instead of falling to a lower tier, carries a machine-readable code and leaves the flow's verified state alone. * fix(flow): ask only about the writes a run sends A run now gates the writes among the requests its output needs, not every write the recording caused; the command replay checks the rest. Adds a Chrome e2e against a local fixture: sign-in cookie, CSRF write through a response value, GraphQL query, a GET side effect, a cross-domain redirect, and two saves in one session. commands.mdx stays as it was because the docs route fixtures freeze it. * fix(flow): close review findings on credentials, redirects, replay and typed values - A 401 or 403 on a request recorded with stripped credential headers falls through to the page and replay tiers; auth_expired is reported when every way is refused. - A blocked redirect returns its 3xx, a write with a 2xx or 3xx answer is accepted, a write with no answer returns outcome_unknown and is never resent, and the in-page write does not follow redirects. - The replay runs only plain interactions, checks every URL in a recorded command, and stops when the tab leaves the flow's site. - keyboard type and inserttext read the focused element; typed params keep no example. - base_domain uses the public suffix list; a flow whose origin directory does not match its start URL must be saved again. - A save of an earlier range no longer moves the save marker back, and challenge pages are recognized only in HTML. * fix(flow): keep vault and key-by-key passwords out of flows and close URL and retry gaps - Replay URL checks read every string the way the URL parser does, so tabs, newlines and leading control characters cannot hide a host, and URL keys are checked whatever the value looks like. - The in-page fetch sends a write once; a thrown fetch is outcome_unknown. - A command that is not recorded tags no requests to the previous step, and the password auth login fills is registered as a secret. - Keys pressed in a secret field are gathered into one secret, redacted from the steps, and requests carrying it are dropped at save. * fix(flow): keep modifier chords and clipboard text out of secret handling gaps A key joins a secret only when it is bare or Shift plus a key, so Control+a no longer corrupts the buffer. A paste chord or clipboard paste into a secret field redacts the step and drops the requests it tagged, and clipboard write text is redacted and registered as a secret. * fix(flow): register a pasted password once the paste has finished After a paste chord or clipboard paste into a secret field, the focused field's value is read and registered as a secret in full, with any typed prefix, and later keys extend it. A request a later step sends with it is no longer saved. When the value cannot be read, the requests tagged to the paste step are still dropped. * fix(flow): replay once per run, recognize encoded secrets and link writes to earlier responses - The command replay runs at most once per run; every request that falls back to it reads its answer from that replay, and it is skipped when another tier already sent a write it would repeat. - Requests carrying a secret are recognized in form-encoded, percent-encoded and JSON-escaped forms. - A write's request takes a value from an earlier response and depends on it, so a rotating token is read fresh at each run. * fix(flow): never resend a replayed write, keep rejected writes eligible, match replays by query - Before any tier sends a write, the run checks whether it was already sent. A write the replay sent is answered from the replay's capture, and stops with outcome_unknown when the capture has no answer. - Only a 2xx or 3xx answer, or an unknown outcome, counts as a sent write; a 4xx rejection leaves the replay available. - Replayed requests match by method, host, path, recorded query and body, ignoring query parameters the recording never had; a missing or ambiguous match fails for that request. JSON bodies compare by value. * fix(flow): read the flow once per run and carry it through confirmation A flow_run loads its file once. Policy, the write gate and the run use that copy, and a confirmation keeps it so the confirmed run executes the file that was checked, not one read again from disk. The write gate is one function used before the run and inside it. * refactor(flow): one secret-field predicate and one header classifier FieldTraits::looks_secret replaces four copies of the type, autocomplete and name check. HeaderClass names the four ways a recorded header is treated, in place of three separate lists. * refactor(flow): type the saved flow and move to a module directory Flow, Step, Request, Write, Param and Tier are serde types with unknown fields rejected, so the file schema is checked on load and a run no longer walks serde_json::Value. Request.write replaces the writes lookups in the run path. The file format is version 1; nothing older has shipped, so the old-version path is gone. Fixtures build flows through serde_json. * refactor(flow): one write ledger and next_tier decide when a request is sent again WriteLedger records which writes a run sent, and next_tier picks the next tier from the attempts so far. They replace the accepted, retry, completed and already-sent checks that were spread over the run loop, the replay and its guard. A write that was sent is only read back from the replay's capture. * refactor(flow): a Sender per tier behind ports, and the run loop in flow HttpSender, PageSender and ReplaySender each live in their own file and implement Sender. The browser session is reached through the Host and CommandRunner ports, which actions.rs implements in one wiring struct, so the replay no longer lives in actions.rs. run_requests is the run loop; an in-memory Senders drives it in tests without a browser or a network. The navigation hint moves into flow. * refactor(flow): the recorder keeps its fields and offers begin, tag, end, steps and save actions.rs no longer reads or writes recorder internals. It enters and exits a command, begins and ends it with what the page said, and asks for the steps or a save. Range resolution, marking a save and the session's saved list live behind save. Step stabilizing moves into the recorder. * refactor(flow): one host policy, one failure constructor, no pass-throughs HostPolicy is the only place a URL is checked against a flow's hosts; the recording's first-party test, the run, the redirect policy, the replay and the page tier all use it. TierResult::fail with Failure::drift, unavailable and unknown_outcome replaces five result builders. replay_match, validate_name's wrapper, write_policy_action and flow_for_command are gone; a Write names its own policy action. * refactor(flow): drop the click-fired write guess and the --from keywords A GET is a suspected write only when its path has a whole logout, signout, unsubscribe, delete or remove segment; guessing from the command that fired it needed the output's dependencies and the request's resource type. --from takes a step number, and without it a save starts at the last navigation after the previous save. Help, MCP, the tool catalog, docs and the skill reference follow. * test(flow): table-driven cases, behavior names, and key tests through the save Hosts, check_response, GraphQL and secret-field cases are tables. The tests named after review comments carry the behavior they check. Key-buffer, paste and clipboard tests ask whether a request carrying the secret survives a save instead of reading the recorder's secrets. * fix(flow): a request is a write only when the writes list says so Request.write was a second copy of what Flow.writes records, and it defaulted to false, so a file could disagree with itself and a write could skip the ledger. The run reads the writes list alone. * refactor(flow): build the flow from its types and put write queries on Flow build_flow and the reference linker create Step, Request and Param directly instead of json! values that were parsed back into the same types. run_writes, replay_only_writes and AllowWrite::check replace four free functions that walked the writes list. * refactor(flow): three tier functions over one Browser port http::send, page::send and replay::send replace the Sender trait and its three structs. Host and CommandRunner become one Browser trait. The Senders seam and its in-memory adapter stay. * refactor(flow): Stop and RunOutcome name why a run ended, and a flow is read once Stop replaces the four string codes that were matched in send, memory and actions. RunOutcome builds the stopped, drift and failed messages with unit tests, so handle_flow_run no longer formats them. The run's host check is the per-request one after references are filled. A flow_run that cannot read its flow fails at the first read, before policy, so policy and the run never see two different files. * fix(flow): tag a request by when it started and wait on conditions in the e2e A request belongs to the command that was running when it started, using the request's own start time against each command's begin time, so a late event read during the next command keeps its step. The replay's wait no longer stalls for its full timeout: a request body that still has references to fill is matched on the request alone. The e2e wait for the server to see a click's request and for the browser to finish reading it, instead of sleeping. * fix(flow): check every request's host before the first one is sent A flow with a foreign host in a later request sent the earlier requests before rejecting it. The run now checks every request URL against the flow's hosts up front, on the template with references unfilled, and again per request once they are filled. * fix(flow): a flow that sends a write it does not list fails to load A request that changes state and is missing from the writes list would run with no gate and no once-only guarantee, so an edited file could drop it. Loading now refuses the file and asks to save it again. * fix(flow): the replay lets each command's requests finish before the next A command that depends on what the previous one fetched, such as a click that sends a token the page stored from the last response, raced the response and sent the wrong body. * refactor(flow): ship flow run over http only Flow run sends the saved requests over direct HTTP. The in-page fetch and command replay tiers, the cross-tier write ledger, recorded commands in the file and the tier preference are removed; a 401 or 403 is auth_expired. Every HTTP guard stays: host policy, write gate, single load, shape verification, redaction and owner-only files. Docs, help, skill reference and the MCP catalog describe the HTTP path only. * fix(flow): report a stripped credential instead of an expired session A request recorded with an authorization, token or CSRF header is marked strippedAuth at save. A 401 or 403 for it stops the run with credential_not_sendable, since signing in again cannot supply what the file does not keep. Other 401 and 403 responses stay auth_expired. The file no longer lists redactedSteps: it holds no steps to point to. * fix(flow): keep params out of GraphQL operations and expand placeholders once A param fills the variables and other fields of a GraphQL body, never the operation text, and an operation with a placeholder in its text counts as a write. After values are filled, a request that classifies as a write but is not in writes is refused before anything is sent, for params and for references. Params and references are expanded in one pass over the template, so a response or param value that contains placeholder text is inserted as data instead of being expanded again, which looped without end. * fix(flow): refuse a read whose filled path names a state change After params and references are filled, a GET whose path has a whole logout, signout, unsubscribe, delete or remove segment and is not listed in writes is refused before it is sent, the same as a request that becomes a write by method or GraphQL operation. * fix(flow): encode values per slot, gate every write, never resend a write on redirect Placeholders are parsed once into parts and bound in a single pass, with each value encoded for where it lands: a URL query or path segment, a form body, or a JSON string. Text that was inserted is never read as a placeholder again. At save, a typed value is found in the form its slot writes it, so quotes, backslashes, newlines, ampersands and plus signs in a JSON or form body become params instead of staying literal. A flow with no output sends every request, and every write among them goes through the write gate. An empty dependency no longer replaces a nonempty output with an empty result. A write never follows a redirect, including a 307 or 308; the 3xx is its answer. * fix(flow): read a form by its shape and refuse values that break a raw body A body shaped k=v&k=v is a form whatever its content type, so a param value with & or = stays inside its field. For a header, a plain text body or a multipart body, a value with a line break, or one that contains the multipart boundary, is refused before anything is sent. * fix(flow): keep path segments shared across requests literal when linking response values A value found in an earlier response replaced a path segment such as /api when the response held the same word (gitRootDirectory = "api"). A segment present in the path of more than one recorded request is site structure, so it is no longer linked to a response. * fix(flow): accept capture request ids for --output and list valid ids on error Saved ids are renumbered within the saved range, while the ids a capture shows keep their step sequence. --output now accepts either form, and an unknown id lists the ids that are valid. * fix(flow): drop per-request tracing and client-identity headers when saving x-client-ip, x-forwarded-for, x-real-ip, forwarded, traceparent, tracestate, x-request-id, request-id, x-amzn-trace-id, sentry-trace, baggage, cf-connecting-ip and true-client-ip identify the machine or one request. Replay sends only the saved headers and reads none of these, so they are classified as dropped next to cookie and host. * fix(flow): do not promote a page-query value that only the Referer carries A start-URL query value counts as sent when any recorded entry, saved or not, contains it, so `?create=true` became a param although only the Referer of the saved requests held it. After the requests are templated, a page-query param used by no URL, body or other header is dropped and the Referer keeps its recorded value. * fix(flow): decide structural values by source instead of how many paths share them A response value is left literal when it is already a path segment of the request it came from or of an earlier one. Counting requests also stopped ids that two later paths use, such as a project id, from becoming params or references. * style(flow): remove the empty line after a doc comment * fix(flow): refuse an --output id that names two different requests When a range starts after step 1, a capture id and a saved id can be the same string for different requests. The error names both meanings. The accepted id forms are documented. * fix(flow): template the restored Referer against the params that remain Restoring the raw Referer when a page-query param is dropped also removed the placeholders of params that are kept. The recorded Referer is now templated against the remaining params. * fix(flow): report drift and signed requests, name dropped secret requests, link ids to their source - A recorded list missing from the response is drifted; a present empty list stays ok. The main list path is saved and checked, including lists deeper than the shape signature. - A 401 or 403 on a request carrying a per-request signature or timestamp reports request_signed; static or word-like values keep auth_expired with a hint naming the header. - A request dropped for carrying a secret-field value is named by method and path with the secret redacted, or by host when the value cannot be located. - Response values link to the latest supplying response that does not echo the value from its own path, preferring lists, so a param chosen from a list carries through detail and nested requests. * fix(flow): keep the recorded list's items when only counts are missing A response that omits a recorded count but still has its list is ok and returns the items; it is empty only when the list itself is empty. * fix(flow): reword capture guidance for the relaunch behavior and validate raw launch capture The launch body's capture value is checked like the per-command setting, the not-capturing message and --capture help describe the relaunch, and the capture e2e asserts it. | 3 天前 | |
fix(build): build the dashboard in source builds (#2286) Source builds (the Homebrew formula runs build:native) embedded the 'Dashboard not built' placeholder without any signal. build:source runs the same dashboard build as the release workflow, and build.rs now warns whenever the placeholder is embedded. Fixes #1412 | 8 天前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 8 个月前 | ||
| 1 天前 | ||
| 2 天前 | ||
| 3 天前 | ||
| 3 天前 | ||
| 8 天前 |