| Give every wiki page a view counter of its own (#1265) * Give every wiki page a view counter of its own A GitHub wiki serves no per-page analytics. The traffic API reports the ten most visited paths over fourteen days and nothing below that, so a corpus of 61 pages is invisible by construction: measured today, not one individual wiki page reaches that top ten on either property. We know which pages rank and not which ones get read. Each page now embeds a 68-byte transparent 1x1 PNG held as an asset of an article-views release, the way the browser launch counter has worked since May: the asset's download count is how many times the page was loaded. The release is created with make_latest=false, so it never becomes the repository's Latest. What that number is not, measured rather than assumed: four deliberate loads produced six counts, so it carries fetches we did not make and cannot separate, download_count being a bare integer with no provenance. It counts fetches and not visitors, crawlers like readers, and nothing at all for a reader who blocks images. It is for comparing pages and days. The pixel goes into the wiki only, never back into docs/: the wiki is the surface that ranks, while the .md in the repository is served as a JSON payload React mounts, so a counter there would measure almost nothing and put markup in the source tree. build_wiki.py writes no pixel unless it is told which repository it builds for, which is right for a local preview and silent otherwise - a wiki published that way looks entirely normal and counts nothing. So the workflow passes the flag and then gates on it: the checker refuses a page with no pixel, with two, or with one addressed elsewhere, and its own eleven known-bad cases run first. * Pace the first upload run, and say it in English The first run on an existing corpus uploads one asset per page and GitHub throttles bursts of content-creating requests with a 403 or 429 carrying Retry-After. That burst happens before the wiki push, so a throttle would have blocked the publish itself on the one run nobody gets to rehearse. Honour the header, retry, and pace the loop at a tenth of a second. The backoff has its own three known-bad cases against a fake throttling server, because retry code that has never once retried is not retry code. A 404 stays unretried on purpose: reading one is how a missing release is found. And the file was carrying Italian identifiers and test names into a public repository. The language gate reads git ls-files, so running it before the file was tracked was running it against a world where the file did not exist. | 14 小时前 |
| Remove the legacy notes about aihawk-do's removal and the dated version tables (#1256) * Remove the legacy notes about aihawk-do's removal and the dated version tables Historical asides that outlived their usefulness, at the owner's request: - The "Recorded against" version tables in both articles (invisible-playwright-mcp 0.3.0, engine firefox-26, dated 2026-09-03/04) - the "Reproducing it" section right below each one already gives a working command, and the table added a stale snapshot nobody needed to reproduce it. - Every code comment and test docstring explaining that `aihawk do` / `runner.drive` was removed on 2026-09-03 (agent.py, link.py, runner.py, test_key_isolation.py, test_real_cli.py, test_cli_surface.py). Where the surrounding paragraph carried a real architectural point (why run_task still exists, why the loop has one consumer, what test_real_cli.py actually proves), it is kept, rephrased without naming the removed command. - The PyPI-shim compatibility sentence in docs/mcp-server.md ("invisible-playwright-mcp lives on... before 0.11.0 keeps working unchanged") - the shim's own README, on the archived package, already documents this in full. Two more inaccuracies caught while touching this code, both stale references to the removed `drive`: test_ui_drive.py compared its spawn to `aihawk.runner.drive`, which does not exist, and named a dead `pkg-cli/tests/` path in its run-it-yourself line; test_agent_loop.py made the same comparison. Both now describe what actually spawns the server today. * Bump to 0.11.1: this PR touches files under src/aihawk, which the version gate treats as shipped | 1 天前 |
| The legacy logo leaves the tree | 4 天前 |
| The review says which versions it read, not what PyPI serves today (#1274) The paragraph said the package "is on PyPI at version 0.3.0". That was true when the review was written on 3 September; the index now serves 0.19.0, so a reader today sees a number sixteen releases behind and reasonably concludes the project has stalled - the opposite of what the repository shows. The sources list already had the right shape for this: it dates its reading ("metadata checked against the index 2026-09-04") and so it cannot go stale. This paragraph now does the same, and points at the PyPI page for the current answer instead of trying to be it. The versions stay in the text. A review of something that ships several times a week has to say which build it looked at, or its claims cannot be checked at all. | 4 小时前 |
| Give every wiki page a view counter of its own (#1265) * Give every wiki page a view counter of its own A GitHub wiki serves no per-page analytics. The traffic API reports the ten most visited paths over fourteen days and nothing below that, so a corpus of 61 pages is invisible by construction: measured today, not one individual wiki page reaches that top ten on either property. We know which pages rank and not which ones get read. Each page now embeds a 68-byte transparent 1x1 PNG held as an asset of an article-views release, the way the browser launch counter has worked since May: the asset's download count is how many times the page was loaded. The release is created with make_latest=false, so it never becomes the repository's Latest. What that number is not, measured rather than assumed: four deliberate loads produced six counts, so it carries fetches we did not make and cannot separate, download_count being a bare integer with no provenance. It counts fetches and not visitors, crawlers like readers, and nothing at all for a reader who blocks images. It is for comparing pages and days. The pixel goes into the wiki only, never back into docs/: the wiki is the surface that ranks, while the .md in the repository is served as a JSON payload React mounts, so a counter there would measure almost nothing and put markup in the source tree. build_wiki.py writes no pixel unless it is told which repository it builds for, which is right for a local preview and silent otherwise - a wiki published that way looks entirely normal and counts nothing. So the workflow passes the flag and then gates on it: the checker refuses a page with no pixel, with two, or with one addressed elsewhere, and its own eleven known-bad cases run first. * Pace the first upload run, and say it in English The first run on an existing corpus uploads one asset per page and GitHub throttles bursts of content-creating requests with a 403 or 429 carrying Retry-After. That burst happens before the wiki push, so a throttle would have blocked the publish itself on the one run nobody gets to rehearse. Honour the header, retry, and pace the loop at a tenth of a second. The backoff has its own three known-bad cases against a fake throttling server, because retry code that has never once retried is not retry code. A 404 stays unretried on purpose: reading one is how a missing release is found. And the file was carrying Italian identifiers and test names into a public repository. The language gate reads git ls-files, so running it before the file was tracked was running it against a world where the file did not exist. | 14 小时前 |
| A declared browser wakes where it was, tabs and all (0.19.0) (#1272) A saved session gave back WHO its browsers were. That is half of what somebody reopens a session for: a browser that comes home as the right person with nothing open is right about its identity and wrong about its work. The urls are saved with the identity now, and the first command aimed at a declared browser reopens them. Click a pane that says "not up" and it starts, with a stopwatch on it, because seven to fourteen seconds of silence is the defect this project fixed elsewhere with the Thinking clock. ⛔ FOURTEEN TOOLS WROTE `registry.ensure(addressed(...))` FOR THEMSELVES, so "what it takes to hand somebody a usable browser" was a fact known in fifteen places. The moment it stopped being just `ensure` - a declared browser now has tabs owed to it - fifteen places would have had to learn the same new step, and the one that did not would hand back a browser that came home empty. They go through one funnel, `ready`, and a test says no tool reaches the registry on its own. The floor on the address scan fell from 18 to 10 because there are fewer places able to get it wrong; that is normally how a gate gets switched off, so the reason is written where the number is and the stronger guarantee replaced it. The reopen happens ONCE. A wake that kept reopening would fight whoever is using the browser, adding the same pages back every time it was handed out. Three things this got wrong. The recovery path brought back half a browser: `drop` keeps the identity on purpose, and until now the pages were not part of "the same person" - a browser that died mid-command came back correct and empty, and the only way to notice was to go looking for your own work. The session file was never rewritten when tabs moved, so the urls were fresh in memory and stale on disk. Saving from `ready` unconditionally would have put a disk write in the path of the frame pump, thirteen times a second, so it writes when the tabs actually changed - which turns "every command" into "every navigation". And the workspace copied the `touched` guard from the picture route, where it belongs because asking for a picture STARTS a browser. `browser_list` starts nothing, by construction and by its own test, so the copy emptied the workspace in exactly the case declarations exist for: a session reopened after a restart, whose panes offering to wake its browsers would never have been drawn. Verified with a real engine: a session file declaring a browser with a url, a restart, and the wake brought it back on that page - then a second browser made the first one a preview showing a real frame. | 5 小时前 |
| A declared browser wakes where it was, tabs and all (0.19.0) (#1272) A saved session gave back WHO its browsers were. That is half of what somebody reopens a session for: a browser that comes home as the right person with nothing open is right about its identity and wrong about its work. The urls are saved with the identity now, and the first command aimed at a declared browser reopens them. Click a pane that says "not up" and it starts, with a stopwatch on it, because seven to fourteen seconds of silence is the defect this project fixed elsewhere with the Thinking clock. ⛔ FOURTEEN TOOLS WROTE `registry.ensure(addressed(...))` FOR THEMSELVES, so "what it takes to hand somebody a usable browser" was a fact known in fifteen places. The moment it stopped being just `ensure` - a declared browser now has tabs owed to it - fifteen places would have had to learn the same new step, and the one that did not would hand back a browser that came home empty. They go through one funnel, `ready`, and a test says no tool reaches the registry on its own. The floor on the address scan fell from 18 to 10 because there are fewer places able to get it wrong; that is normally how a gate gets switched off, so the reason is written where the number is and the stronger guarantee replaced it. The reopen happens ONCE. A wake that kept reopening would fight whoever is using the browser, adding the same pages back every time it was handed out. Three things this got wrong. The recovery path brought back half a browser: `drop` keeps the identity on purpose, and until now the pages were not part of "the same person" - a browser that died mid-command came back correct and empty, and the only way to notice was to go looking for your own work. The session file was never rewritten when tabs moved, so the urls were fresh in memory and stale on disk. Saving from `ready` unconditionally would have put a disk write in the path of the frame pump, thirteen times a second, so it writes when the tabs actually changed - which turns "every command" into "every navigation". And the workspace copied the `touched` guard from the picture route, where it belongs because asking for a picture STARTS a browser. `browser_list` starts nothing, by construction and by its own test, so the copy emptied the workspace in exactly the case declarations exist for: a session reopened after a restart, whose panes offering to wake its browsers would never have been drawn. Verified with a real engine: a session file declaring a browser with a url, a restart, and the wake brought it back on that page - then a second browser made the first one a preview showing a real frame. | 5 小时前 |
| Make a failed run say so, and stop shipping a file people fill with secrets (#1168) * Make a failed run say so, and stop shipping a file people fill with secrets Three problems that all showed up as "it does nothing". A run that could not start printed nothing and exited 0. loguru begins with no sinks, src/logging.py removes them again and only adds one if LOG_TO_FILE or LOG_TO_CONSOLE is set, and both were False. main.py catches every exception, hands it to that silent logger and falls off the end of the function. Measured with a required file removed: empty stdout, empty stderr, exit 0, and the shell reporting success. Every other first-run problem went the same way, a wrong key, no Chrome, an unreachable posting. Console logging is on and the handlers exit 1. data_folder held the file the setup instructions tell you to put your API key in, and it was tracked. Filling it in staged a live credential on the next commit, which is exactly how one got in here. Same for plain_text_resume.yaml, which wants your address and phone number. Those three files are untracked and ignored now, and main.py creates them from data_folder_example on first run and tells you what to edit, so the manual copy step is gone as well. The error message pointed at Auto_Jobs_Applier_AIHawk, a name this repository has not had for a while. Tests for all of it, each checked against the failure it describes: reverting the console flag turns both visibility tests red, and the placeholder check now refuses six credential prefixes rather than one, verified against four. * Drop the sponsor banner The README opened on a banner reading "The best undetectable browser", a superlative nothing here or in the packages benchmarks, sitting directly above the section that spends a paragraph explaining the browser is one input into a risk score among several. invisible_playwright is still linked where it belongs, under the pieces the project is made of. The image stays in assets/ since other repositories reference that URL. | 7 天前 |
| Relicense under MIT Replaces AGPL-3.0 with the MIT text the other projects in this account carry. The copyright line names this project and starts at 2024, when the repository was created, rather than being copied verbatim from a different one. This applies going forward only. Every copy already distributed, and the forks that exist today, remain under the AGPL they were received under: a licence that has been granted is not withdrawn by changing this file. | 5 天前 |
| Identify the app to OpenRouter, and default to GLM 5.3 Flash (0.12.0) (#1257) Two changes to what leaves the process for the model. OpenRouter groups its public rankings by HTTP-Referer and X-Title, and this package sent neither, so every request was anonymous traffic on the key. They go on the client rather than the call site: one place, and every request carries them whatever calls it. Nothing fails when they are missing - no error, no changed answer, the app simply is not there - so the tests assert them on the wire against a local stub server rather than on a client attribute, which is also how the key is asserted a few tests above. The default model moves from z-ai/glm-4.6 to z-ai/glm-5.3-flash: input is $0.071-$0.388 per million against $0.43-$0.60, output $0.237-$1.358 against $1.75-$2.20, and the context window is 1,310,720 tokens against 204,800. Anyone who set --model or AIHAWK_MODEL is unaffected. The wiki carried the old default in four pages, three of them with cost arithmetic resting on its prices; all four are updated with figures retrieved today, and the section arguing that a cheaper model which retries is not cheaper now says plainly that the caveat applies to the new default too, since there is no measurement here of how it holds up over twenty turns of tool calls. One test pinned the default as a literal copy and now reads the constant, which is the drift the test beside it warns about. | 21 小时前 |
| A declared browser wakes where it was, tabs and all (0.19.0) (#1272) A saved session gave back WHO its browsers were. That is half of what somebody reopens a session for: a browser that comes home as the right person with nothing open is right about its identity and wrong about its work. The urls are saved with the identity now, and the first command aimed at a declared browser reopens them. Click a pane that says "not up" and it starts, with a stopwatch on it, because seven to fourteen seconds of silence is the defect this project fixed elsewhere with the Thinking clock. ⛔ FOURTEEN TOOLS WROTE `registry.ensure(addressed(...))` FOR THEMSELVES, so "what it takes to hand somebody a usable browser" was a fact known in fifteen places. The moment it stopped being just `ensure` - a declared browser now has tabs owed to it - fifteen places would have had to learn the same new step, and the one that did not would hand back a browser that came home empty. They go through one funnel, `ready`, and a test says no tool reaches the registry on its own. The floor on the address scan fell from 18 to 10 because there are fewer places able to get it wrong; that is normally how a gate gets switched off, so the reason is written where the number is and the stronger guarantee replaced it. The reopen happens ONCE. A wake that kept reopening would fight whoever is using the browser, adding the same pages back every time it was handed out. Three things this got wrong. The recovery path brought back half a browser: `drop` keeps the identity on purpose, and until now the pages were not part of "the same person" - a browser that died mid-command came back correct and empty, and the only way to notice was to go looking for your own work. The session file was never rewritten when tabs moved, so the urls were fresh in memory and stale on disk. Saving from `ready` unconditionally would have put a disk write in the path of the frame pump, thirteen times a second, so it writes when the tabs actually changed - which turns "every command" into "every navigation". And the workspace copied the `touched` guard from the picture route, where it belongs because asking for a picture STARTS a browser. `browser_list` starts nothing, by construction and by its own test, so the copy emptied the workspace in exactly the case declarations exist for: a session reopened after a restart, whose panes offering to wake its browsers would never have been drawn. Verified with a real engine: a session file declaring a browser with a url, a restart, and the wake brought it back on that page - then a second browser made the first one a preview showing a real frame. | 5 小时前 |