Skip to content

feat(tabs): auto mode — watch the browser, propose scripts, hear the answer - #30

Merged
colevels merged 4 commits into
mainfrom
feat/auto-mode-bridge-v2
Jul 19, 2026
Merged

feat(tabs): auto mode — watch the browser, propose scripts, hear the answer#30
colevels merged 4 commits into
mainfrom
feat/auto-mode-bridge-v2

Conversation

@colevels

@colevels colevels commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Turns the one-shot tabs push handoff into a loop an agent can actually run: the
extension streams tab state as it changes, the agent proposes a script with a
plain-language note
, and the user's Accept/Deny comes back over the same bridge.

Extension side is colevels/tabbrew#95
merge and release this first, it is inert until the extension speaks protocol 2.

Why

Three things made the existing flow impossible to loop on:

  • Tab state froze at click time. The agent could only ever reason about the
    snapshot from the moment the user clicked the card.
  • A pushed script arrived with no explanation. The wire carried { script }.
    The user saw a DSL diff, not "จะปิด 6 แท็บ YouTube แล้วรวม github เป็นกลุ่ม Code".
  • Nothing came back. push printed "✓ Sent" and exited, so a loop would
    re-suggest the thing the user just threw away, forever.

What's here

Bridge protocol v2 (tabs-serve.ts) — every posted tab state gets a version and
keeps the full SnapshotPayload (groups/windows/the extension's own rendered
markdown, so the CLI never has to reimplement renderSnapshot). Adds a long-poll
GET /tabs?since, the suggestion/decision round trip, and GET /history.
POST /tabs and GET /script keep their protocol-1 shapes byte for byte.

tabs watch — blocks until the tabs actually change, then prints what moved plus
the snapshot. A timeout prints nothing and exits 0, so a loop branches on empty
output rather than an exit code.

tabs suggest — a separate command from push, not two flags on it: --note is
required (a suggestion nobody asked for has to explain itself, and only a required
flag makes that reliable), and it waits for the verdict. Always exits 0 — a Deny is
an answer, not a failure.

tabs history — a delta per version, not a snapshot (500 snapshots of 200 tabs
would be a 20 MB file). This is the one place the CLI accumulates browsing history at
rest — tabs.json is overwritten and only holds open tabs, this log remembers closed
ones — so it is 0600, capped at TABBREW_TABS_HISTORY_MAX (500), and switchable off
via tabs serve --no-history / TABBREW_TABS_HISTORY=0 / tabs history --clear.

tabbrew-auto skill — the loop written down, installed by init alongside
tabbrew-tabs. It is the first skill whose source of truth is this repo (it
documents commands that don't exist in tabbrew-api, so it must never be re-synced from
there), and it deliberately contradicts tabbrew-tabs on one point: no in-chat DEL
confirmation, because the panel's Accept card is the confirmation. AgentTarget
grows from one skill to a list.

Found by running it against a real browser

  • Bun.serve defaults to a 10-second idleTimeout and killed every long-poll this
    server exists to hold open. The symptom lied: the socket died mid-wait and tabs watch reported "nothing is listening" about a server that was running fine.
  • Both long-polling clients now confirm with /health before blaming the bridge — a
    dropped connection and a dead server are different events and only one is worth
    telling the user about.
  • Added a failed decision for "the user accepted and Chrome refused". Reporting that
    as accepted tells a watching agent its plan landed when the tabs never moved.

Verified

typecheck + test + build green. End-to-end against a real Chrome with the
extension branch loaded: auto uplink posting source: auto with groups/windows/
snapshot, tabs watch unblocking on a real tab change and naming the closed tabs,
Deny returning {"decision":"denied","reason":"don't manage my youtube"}, Accept
returning {"decision":"accepted","opCount":1} with the tabs actually regrouped
(Social 1 → 4) and the delta log recording ~3. History cap verified by driving 70
posts at --max 10 (settles at 19 lines, stays 0600), and --no-history writes no
file at all.

🤖 Generated with Claude Code

colevels and others added 4 commits July 20, 2026 03:52
…tory

The one-shot handoff can't back an agent loop: the tab state freezes at the
moment the user clicks, a pushed script arrives with no explanation, and the
CLI never learns whether it was applied. This adds the missing half.

- tabs serve: version every posted tab state, keep the full SnapshotPayload
  (groups/windows/the extension's rendered markdown), and long-poll on
  GET /tabs?since so a watcher wakes only when something moved. Adds the
  suggestion/decision round trip. GET /script and POST /tabs keep their exact
  old shapes, so extension builds in the wild are unaffected.
- tabs-history.ts: append one *delta* per version (not a snapshot — 500 of
  those would be a 20 MB file), capped and 0600. It's the one new data-at-rest
  risk here, so: --no-history, TABBREW_TABS_HISTORY=0, and history --clear.
- tabs watch: block until the tabs change, then print what moved plus the
  snapshot in the exact format the skill reads.
- tabs suggest: propose a script with a REQUIRED --note and wait for the
  verdict. Separate from `push` because push is fire-and-forget and note-free;
  a suggestion the user didn't ask for has to explain itself, and the loop has
  to hear "no" and why.
- tabs history: show (or wipe) that change log.

SUMMARY_MAX drops 59 → 57: `tabs suggest <file>` is now the widest label, so
two existing summaries were trimmed to keep help inside 80 columns.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The watch loop needs instructions of its own, and they contradict the
interactive skill on purpose: `tabbrew-tabs` requires listing every DEL target
in chat and getting a "yes" before emitting, while in auto mode the panel's
Accept/Deny card IS the confirmation — asking again in a chat the user may not
even be looking at just makes them approve the same thing twice. Two skills,
not one skill with a mode.

SKILL.auto.md is the first skill whose source of truth is this repo: it
documents `tabs watch`/`tabs suggest`, which don't exist in tabbrew-api, so it
must never be re-synced from there. Noted in skills.ts and CLAUDE.md.

AgentTarget grows from one skill to a list (`skillNames` +
`resolveSkillsDir(scope, name)`), and init loops it across install, uninstall,
and dry-run. `--variant` still applies only to tabbrew-tabs — the loop's
instructions don't get cheaper with more tabs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ong-poll

Two things found by running this against a real browser:

Bun.serve defaults to a 10-SECOND idleTimeout and kills any request that quiet
— which is every long-poll this server exists to hold open. The symptom lied:
the socket died mid-wait, the client's fetch rejected, and `tabs watch` reported
"nothing is listening" about a server that was running fine. idleTimeout: 0;
the handlers already cap their own wait.

Both long-polling clients now confirm with /health before blaming the bridge,
and otherwise re-poll with the time that's left — a dropped connection is not
the same event as a dead server, and only one of them is worth telling the user
about.

Adds a `failed` decision for "the user accepted and Chrome refused". Reporting
that as `accepted` tells a watching agent its plan landed when the tabs never
moved, and it will build the next suggestion on a browser state that never
existed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
VERSION is read from package.json at compile time, so this bump is what makes
an installed `tabbrew update` see the auto-mode build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@colevels
colevels merged commit 3d1f665 into main Jul 19, 2026
1 check passed
@colevels
colevels deleted the feat/auto-mode-bridge-v2 branch July 19, 2026 21:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant