Releases: daimou1028/hadome
Release list
hadome 0.1.18 — tables, lists and quotes in replies
ChatGPT answers in Markdown, and the panel only understood part of it: code blocks, bullets, headings and bold. Everything else arrived as raw characters — a table became a column of | a | b | lines, --- and > quote were printed literally — and bold broke whenever the bold text itself contained an asterisk.
| Before (0.1.17) | After (0.1.18) |
|---|---|
![]() |
![]() |
The reply in the screenshots is made up. Both were taken in a throwaway editor with the same input.
What now renders
- Tables — only when a separator row (
|---|) follows the header, so a stray line that starts with|stays text. Cells keep inline code and bold. In a narrow panel the table scrolls sideways instead of breaking words in half. - Numbered lists — as real lists. A list split by blank lines keeps its numbers (
start), so "1 … 2 … 3" does not turn into "1 … 1 … 1". - Horizontal rules and quotes — a line of
---,***or___, and consecutive>lines as one quote. - Bold that contains
*—**… deploy-*.py …**used to print the asterisks around the whole sentence. Bold now closes at the nearest**, anda ** b ** cis still plain text. - Indented code fences in the final summary — the conversation itself already handled these; the summary line at the end of a run did not.
Colours come from your editor theme through the panel's existing colour roles, not hard-coded values.
Re-rendering the 297 most recent answers from real sessions: answers that still showed raw ** went from 2 to 0. One long answer that had printed 10 table lines and 7 numbered items as plain paragraphs now renders as 1 table, 7 list items, a rule and a quote.
Also in this release
The "Thinking" toggle is back — and the tab decides whether to show it. chatgpt.com has moved that control three times (there → gone → there but unpressable → pressable). Instead of assuming, the tab now reports whether the control exists, whether it responds and whether it is on, and the panel shows the toggle only when it does. New setting: chatgptBridge.thinking (default false).
The tab protocol moved from 59 to 60, so the Chrome companion must be updated to 0.60.0. A 0.59.0 companion is refused with a message saying which side is old, not silently.
Launched browsers are recorded. tools/bridge-browser.js writes who started which browser (pid, profile, port), so an isolated browser can later be told apart from the one you use every day.
The bundled pre-commit hook no longer breaks the main checkout when you commit from a git worktree. It passed git's GIT_DIR on to the self-check, and a git init inside the check rewrote core.bare to true in the main repository. A failing check now also prints why it failed.
Verification
npm run check: 751 passed. UI recordings: 22 / 22 interactions, 37 / 37 scenes, 14 / 14 measurements. Every new rendering rule has a test that was confirmed to fail when the rule is broken on purpose.
The live end-to-end suite against a real ChatGPT tab (verify:live) was not run for this release.
Install: download hadome-0.1.18.vsix, then in VS Code / VSCodium run Extensions: Install from VSIX…. The Chrome companion is hadome-chrome-0.60.0.zip — replace the 0.59.0 one: remove it in chrome://extensions, then Developer mode → Load unpacked, and reload your ChatGPT tab. The two versions must match.
hadome 0.1.17 — the deny list, in the right order
Six holes in the permission layer, three of them critical. All were found by two AI
reviewers reading the diff, then reproduced here by actually running the command before
being fixed. Two of the three criticals were introduced by the previous release.
The deny list was judged by the order of its rows
denyReason returned the first matching rule and stopped. A command can match more
than one — and whether "allow once" could get through was decided by which rule happened
to sit higher in the table:
echo --output=/tmp/x --remote-debugging-port=9444
matches: --output (overridable)
--remote-debugging-port (never overridable)
returned: --output
→ ran, on a single "allow once"
The rule that exists specifically to stop the agent opening a debug port on your real
browser profile was walked straight past, because a more permissive rule came first in a
list. Every matching rule is now evaluated; one non-overridable match is enough to refuse,
and the refusal names the strictest reason rather than the mildest.
Two guards stopped adding up when they overlapped
A command that hit both the deny list and the protected-path guard used to be an absolute
wall. After 0.1.16 made some deny entries overridable, the combination became passable:
node -e 1 <workspace>/.git/config
→ ran on "allow once", and .git/config was written
The decision in 0.1.16 was per deny-entry. It was not a decision about what happens when a
deny entry overlaps another guard. The override now applies only when the deny list is the
only thing objecting.
Revoking "always allow" now reaches the turn that is already running
The grant lives in the running turn's memory; revoking only rewrote the setting, which the
next run reads. You could revoke a permission and watch the agent keep using it.
All five kinds are covered — commands, reading outside the workspace, writing outside it,
MCP servers, sites — and subagents get the revocation too, which they previously did not.
Three smaller things fell out of the same review: a failed revocation check used to resolve
as "not revoked" (it now refuses and re-asks), re-granting after a revoke did not clear the
revocation record, and the record was cleared too late in startup, so a revocation made
while the run was still confirming got wiped.
edit_file skipped the overwrite guard
write_file refuses to overwrite a file the agent has not read, or one that changed after
it read it. edit_file — the tool whose whole job is touching existing files — did not.
Matching old_text looks like enough until you notice that a lucky guess works on a file
that was never read, and that editing a file by hand while the agent works gets quietly
overwritten as long as the old text is still there. All three shapes now go through it:
replace, insert-at-line, and append.
node -e walked past the deny list entirely
The pattern ate the space after node, and the next group wanted another one, so the form
everyone actually types never matched: node -e, node --eval, node -p, node -r. Only
node <something> -e was ever blocked. There is now a test that runs every listed shape
through the table, plus seven commands that must not match.
Naming one element when twenty share its name
Role plus name cannot pick out a button when every row of a table has one called "Edit".
You can now nest the query, and it reads the way the accessibility snapshot prints:
{"role":"row","name":"Tanaka","中":{"role":"button","name":"Edit"}}
Not an index. An index means something different the moment the page changes — the same
problem that made us reject opaque element handles in the first place. Nesting survives
re-renders because "the Edit button in Tanaka's row" is a description, not a position.
The isolated browser no longer pretends to host your ChatGPT tab
It was launched pointing at chatgpt.com, but the companion extension lives in your normal
browser, not in the isolated profile — so that tab could never connect. The failure looked
like "no tab is connected" while a tab was plainly sitting right there. It now starts on
about:blank and exists only to be driven by the browser_* tools, and --status tells
you which browser to open the tab in instead of walking you through installing an extension
you do not need there.
Relatedly, when the bridge is asked where the tab went, it now names only a window that is
actually holding it. It used to name any live window, which sent you to run /exit somewhere
that would change nothing.
Install: download hadome-0.1.17.vsix, then in VS Code / VSCodium run
Extensions: Install from VSIX…. The Chrome companion is hadome-chrome-0.59.0.zip,
loaded once via chrome://extensions → Developer mode → Load unpacked.
hadome 0.1.16 — the permission layer, measured
Five holes in the permission layer, all found by two AI reviewers reading the same diff,
then measured here one at a time. Every fix ships with a test that was deliberately
broken first to prove it catches the regression.
Revoking "always allow" now reaches the turn that is already running
You could revoke a permission and watch the agent keep using it. The grant lives in the
running turn's memory; revoking only rewrote the setting, which the next run reads.
The panel now remembers what you revoked and every gate consults it — commands, reading
outside the workspace, writing outside the workspace, MCP servers, and sites. All five.
The list is cleared when a new run starts, so re-granting still works.
The manual said "takes effect from the next run". That sentence was a stopgap. It's gone.
node -e <anything> walked straight past the deny list
The rule started ^node(\s|$), which ate the space, and the next group asked for another
one. So the form everyone actually types was never matched:
node -e 1 passed
node --eval 1 passed
node -p 1 passed
node -r ./a passed
node x -e 1 blocked ← only this shape worked
A lookahead fixes it. The wider lesson: the deny list was checked for what it said, never
for whether those commands actually stop. There is now a test that runs every listed
shape through it, plus seven commands that must not match.
Denied commands: "allow once" now means something, per entry
The gate asked you, then threw your answer away — and the refusal said "the user did not
allow this" even when you had just allowed it.
Each deny entry now declares whether a single "allow once" gets through:
| entry | overridable |
|---|---|
--output outside the workspace |
yes |
git branch -D and friends |
yes |
node -e / --require / … |
yes |
--remote-debugging-port |
no |
launching a browser with --user-data-dir |
no |
The last two are the isolation promise itself — if talking the agent into a prompt can open
a debug port on your real profile, "it only touches its own browser" is a habit, not a
guarantee. "Always allow" is still never offered for a denied command: granting it would put
git or node wholesale onto the allow list.
Deny rules you add yourself are not overridable. You added them; you can remove them.
edit_file skipped the overwrite guard
write_file refuses to overwrite a file the agent has not read, or one that changed after
it read it. edit_file — the tool whose whole job is touching existing files — did not.
Matching old_text looks like enough protection until you notice two gaps: a lucky guess at
the text works on a file that was never read, and if you edit a file by hand while the agent
is working, your change is quietly overwritten as long as the old text is still in there.
All three shapes now go through the guard: replace, insert-at-line, and append. Append is
not additive — it strips a trailing </body></html> before adding — so it needed it too.
A failed "remember this" could take the whole run down
When you press "always allow", the setting write is handed off. On two of the four gates it
was not awaited and its failure was not caught. The expectation was "worst case, it forgets".
Measured: an unhandled rejection ends the process. The run disappears.
Install: download hadome-0.1.16.vsix, then in VS Code / VSCodium run
Extensions: Install from VSIX…. The Chrome companion is hadome-chrome-0.59.0.zip,
loaded once via chrome://extensions → Developer mode → Load unpacked.
hadome 0.1.14 — no stray blank tab, and dropdowns stay findable
Two things the previous release left behind, both found by using it.
The stray about:blank tab
Every session started with one. That was us: launching the browser needs a startup URL
(without one Chrome opens the network-loading New Tab Page, or restores your last
session), so we passed about:blank — and then browser_open created a second tab
for the real page. The blank one just sat there.
A blank page has nothing to lose, so browser_open now navigates it instead of opening
another tab. Cold start goes from 2 tabs to 1.
A dropdown's name changes once you pick something
Seen in a real run:
browser_click "月" ✗ not found (same role: 月 1 月 / 性別 男性 / …)
Before selecting it is combobox "月". After picking 1 月 it becomes
combobox "月 1 月" — the current value gets appended to the accessible name. So a name
copied from an earlier snapshot stops matching.
If exactly one element's name starts with what you asked for, that one is used. This
is narrower than loosening the match: 送信 still refuses to hit 送信を取り消す,
because those are two separate elements and both match.
Related: setting a custom dropdown used to report an empty value. The locator goes stale
the instant the name grows, so the read-back saw nothing. It now holds an element handle,
which keeps pointing at the same element regardless of the name.
Measured
From a dead browser, using the code unpacked from the installed extension:
browser_open 1377 ms, 1 tab, no about:blank
browser_set "1 月" → set
browser_set same old name again, "2 月" → set
npm run check — 728 checks, RC=0. Each new guard was deliberately broken to confirm
the check fails.
Install
hadome-0.1.14.vsix→ VS Code / VSCodium → Extensions → Install from VSIX…hadome-chrome-0.59.0.zip→ unzip →chrome://extensions→ Load unpacked- Reload the window after installing.
AGPL-3.0-or-later. Third-party notices in NOTICE.md and licenses/.

