Problem
An agent calling tabbrew tabs repeatedly cannot tell which tabs it has already been shown and which are new since its last look. lastAccessed (already on TabSnapshot) answers a different question: when the user last focused the tab, not when the CLI last reported it.
Proposal
Add seenAt?: number to tab output: the time the CLI previously returned a tab with this URL. Absent means never seen, same convention as lastAccessed (absent is not zero).
Owned by the CLI session server, not the extension
- Only the CLI knows what it actually showed the caller. The extension only knows it answered an operator call.
- The harness and Web Store extensions have different ids, so
chrome.storage would be split between them and lost on reinstall.
- Storing it in the extension would need a new write-back operator for data the CLI already owns. The extension stays a stateless pass-through of Chrome APIs.
- Precedent:
createWindowLabels() in src/core/session/labels.ts already keeps a session-side map and stamps every output that passes through the server.
Keyed by URL
Chrome tab ids do not survive a browser restart, so URL is the only key that works across sessions. URL alone is enough, no parallel tabId map:
- A tab that navigates to a new URL reads as unseen, which is correct (new content).
- Two tabs on the same URL share one
seenAt, which matches "this page has been seen".
- Strip
#fragment before keying. Keep the query string, since many sites use it to distinguish pages.
Stored in ~/.tabbrew/seen.json, single writer
STATE_DIR already exists in src/core/session/config.ts.
- The session server is the only writer: load on start, stamp the previous
seenAt onto the output, update memory, then flush via temp file + rename. Individual CLI processes must not write the file, or concurrent calls would clobber each other.
- The server exits after
IDLE_EXIT_MS, so flush on change (or a short debounce), not on exit.
- A corrupt or unreadable file reads as empty, like
parse() in src/core/session/project.ts. It must never keep a session from starting.
Open questions
- Privacy: the file is effectively browsing history on disk. Mode
0600, removed by uninstall (check whether uninstall already removes all of STATE_DIR), and incognito windows skipped.
- Size: prune by age (e.g. entries older than 90 days) or cap the entry count.
- Multiple Chrome profiles: shared or separate? Suggest shared to start, since the question is "has the agent seen this URL", which does not depend on the profile.
Implementation sketch
src/core/session/seen.ts, following the shape of createWindowLabels(), with tests.
- Wire into the same stamping path in
src/core/session/server.ts.
- Surface
seenAt in src/core/tabs/format.ts.
Problem
An agent calling
tabbrew tabsrepeatedly cannot tell which tabs it has already been shown and which are new since its last look.lastAccessed(already onTabSnapshot) answers a different question: when the user last focused the tab, not when the CLI last reported it.Proposal
Add
seenAt?: numberto tab output: the time the CLI previously returned a tab with this URL. Absent means never seen, same convention aslastAccessed(absent is not zero).Owned by the CLI session server, not the extension
chrome.storagewould be split between them and lost on reinstall.createWindowLabels()insrc/core/session/labels.tsalready keeps a session-side map and stamps every output that passes through the server.Keyed by URL
Chrome tab ids do not survive a browser restart, so URL is the only key that works across sessions. URL alone is enough, no parallel tabId map:
seenAt, which matches "this page has been seen".#fragmentbefore keying. Keep the query string, since many sites use it to distinguish pages.Stored in
~/.tabbrew/seen.json, single writerSTATE_DIRalready exists insrc/core/session/config.ts.seenAtonto the output, update memory, then flush via temp file + rename. Individual CLI processes must not write the file, or concurrent calls would clobber each other.IDLE_EXIT_MS, so flush on change (or a short debounce), not on exit.parse()insrc/core/session/project.ts. It must never keep a session from starting.Open questions
0600, removed byuninstall(check whetheruninstallalready removes all ofSTATE_DIR), and incognito windows skipped.Implementation sketch
src/core/session/seen.ts, following the shape ofcreateWindowLabels(), with tests.src/core/session/server.ts.seenAtinsrc/core/tabs/format.ts.