Skip to content

tabs: persist a seenAt stamp per URL so an agent can tell which tabs are new since it last looked #71

Description

@colevels

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

  1. 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.
  2. Size: prune by age (e.g. entries older than 90 days) or cap the entry count.
  3. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions