Omniscio documentation
Browse all documentation
  1. Getting Started13
  2. Sessions & Agents115
  3. Inbox & Notifications59
  4. Projects & Tasks95
  5. Automation & Scheduling75
  6. Knowledge & Memory26
  7. AI Features60
  8. Integrations100
  9. Plugins & Marketplace33
  10. Cloud & Teams56
  11. Settings & Customization58
  12. Account & Billing28
  13. Troubleshooting84
  14. CLI & API Reference22
  15. Legal & Policies4
  16. Uncategorised22

Reading Queue (Marketplace plugin — save links, auto-summary, read aloud)

The Marketplace-only Reading Queue plugin: saving links, pasted text and PDFs, AI summaries you can highlight, note and search, and read-aloud — plus the acceptance checklist to re-check after any change to its UI.

What it is

The Reading Queue is a place to save things to read later. Save a web link, paste some text, or pick a PDF — it summarizes the content with AI so you can skim the key points without reading the whole article. You can highlight passages, keep notes, tag and search items, and have the summary or the full article read aloud, one item or your whole unread list back-to-back.

It shows up in the sidebar as "Reading" once installed.

Where to find it

You install it from the Marketplace; after that it appears as the "Reading" item in the sidebar, next to your other projects and plugins. Everything the plugin offers lives inside that screen — a list pane and a detail pane, the composer for adding an item, the summary card above the article body, the highlight and note tools, and the play-all control for reading your unread list aloud.

How it behaves

It is a plugin, not a built-in

This is the single most important fact for anyone changing it: Reading Queue is a first-party, Marketplace-only, sandboxed plugin living at src/plugins/reading-queue/. It is not core app code.

  • Users install it from the Marketplace. It is deliberately excluded from the app bundle (BUILTIN_EXCLUDE in plugin-loader.ts and scripts/build-plugins.mjs, which must mirror each other), so the core app stays lean.
  • Its UI is hand-authored vanilla JS served into an isolated webview — no React, no bundler, no build step, and no access to the app's component kit. Same model as Foundry (prdstack) and RepoGuard.
  • It reaches the host only through window.AgentMC — the db, ai, http, tts, toast, shell, settings, and theme namespaces. It cannot import src/renderer/** or @shared/**.

It used to be a built-in "virtual project" (__reading_queue__) gated behind a Settings → Lab toggle. That native implementation — the renderer feature, the main-process backend, the reading-queue:* IPC channels, the CLI routes, and the gating — was deleted in full on 2026-07-22. There is no fallback and no Lab toggle any more. If you are looking for ReadingQueueView, readingQueueEnabled, or READING_QUEUE_PROJECT_ID, they are gone on purpose.

How it stores data

The plugin owns a reading_items collection declared in its manifest (storage.collections) — the host creates and evolves the table. Deletes are soft (is_deleted 0/1) so Undo is just flipping the flag back.

The old native table, reading_queue_items, is a tombstone: it still exists in the database and its historical migrations are untouched, but nothing reads or writes it. Existing rows were deliberately not migrated into the plugin — the migration was a "start fresh" decision. The table is never dropped (the migration ledger is additive-only, and dropping it would destroy user rows). It stays classified 'merge' in all-table-merge.ts so a backup still round-trips those old rows.

Read-aloud and cost

Read-aloud runs on the plugin tts capability: a permission-gated bridge method whose host-side implementation (synthesizeForPlugin) enforces a per-plugin daily spend cap and books the cost under the plugin's own label. That cap is $5 per plugin per calendar day, shared with the plugin's AI calls — read-aloud and generation draw on the same five dollars — and it resets at local midnight; see Plugin Bridge Capabilities for the full rule. The plugin receives audio bytes and plays them itself — it does not drive the app's TTSPlayer, and the old reading-item-<id> pseudo-session coupling no longer exists.

Behavior checklist — what "working" means

These are the acceptance criteria to re-check after any UI change.

They replace the old FR-1..17 list, which lived on this page until the migration retargeted it to the plugin. That list was written against the native React UI — it named SectionHeader, CenteredSpinner, Pill and max-w-prose, none of which the sandbox can import (the-sandbox-may-not-import-core) — so each item is restated here as user-visible behavior. The original FR numbers are kept so older notes still resolve; the range was never contiguous, and only 1, 2, 5, 6, 9, 10, 11, 12, 13, 14 and 15 ever existed.

# Behavior Where
FR-1 A bare domain (theverge.com/article) is normalized to https:// before saving. Multi-line input always saves as text, never a link. A live hint reads "Saving as: Link" / "Saving as: Text" as you type. ui/lib/url-detect.js
FR-2 For a link item, the source URL in the detail header opens in the system browser. ui/views/detail.js
FR-5 There is no permanent "summarized" pill — ready is the silent steady state. pending shows a "Summarizing" pill; failed shows a "Failed" pill beside a Retry button. ui/views/list.js
FR-6 Both panes show a loading state while fetching, never an empty state. ui/views/app.js, views/detail.js
FR-9 The list header reads "Reading Queue" with an "N unread" subtitle. ui/views/list.js
FR-10 With no API-key account configured, a notice points at Settings → Accounts. ui/views/list.js
FR-11 Play-all reports live progress ("Stop, playing 2 of 5") and the playing row shows a pulsing dot. ui/views/list.js, ui/styles.css
FR-12 The summary renders as a hero card above the body; the full text is collapsible and starts collapsed when a ready summary exists. ui/views/detail.js
FR-13 Delete happens immediately with an Undo toast — no confirm dialog. ui/views/app.js
FR-14 Row actions reveal on keyboard focus as well as hover; Arrow Up / Arrow Down move focus between rows. ui/views/list.js, ui/styles.css
FR-15 On a narrow viewport the layout is a single-pane stack (list → tap → detail with Back), never a side-by-side split. ui/views/app.js
— The composer shows "Add (Ctrl+Enter)" and a labelled "PDF" button, and the empty state explains what the queue is for. ui/views/list.js

Two behaviors cannot be checked without spending money: a summary actually reaching ready, and read-aloud actually producing audio. Verify those by hand in a dev instance, and note the summary path needs a stored API-key account — an OAuth login does not satisfy ai.isConfigured(), because the Messages API cannot use an OAuth token.

For agents

How the pieces fit

Concern Where
Manifest src/plugins/reading-queue/manifest.json
UI shell ui/index.html, ui/plugin.js, ui/styles.css, ui/views/
Data layer ui/lib/data.js (over AgentMC.db)
Ingest ui/lib/ingest.js — URL via AgentMC.http, text, PDF via vendored pdf.min.mjs
Summaries ui/lib/summarize.js (AgentMC.ai.generateStructured)
Read-aloud ui/lib/audio.js (AgentMC.tts.synthesize, played in the plugin's own <audio>)
Pure logic ui/lib/highlights.js, url-detect.js, search.js, playqueue.js
Tests tests/unit/plugins/reading-queue/

Notable consequences of the sandbox model:

  • Search runs in JS, not SQL — the plugin db has no LIKE/FTS, so the UI fetches non-deleted rows and filters them in memory (ui/lib/search.js).
  • SSRF protection is the host's job. AgentMC.http.fetch enforces the policy in main; the plugin only does a cheap http:/https: check for UX. That bridge returns a plain serializable object, never a Response (a Response cannot cross contextBridge).
  • PDFs are parsed in the webview with a vendored pdf.js, not in main.
  • Summaries never throw — a failure writes summary_status: 'failed' plus a humanized summary_error, so one bad item cannot break the list.
  • No credentials are reachable. The plugin detects configuration via AgentMC.ai.isConfigured() / AgentMC.tts.isAvailable(), never by reading a key.

Changing it

Read reading-queue-contract.md first — it holds the invariants (core-never-re-grows-a-reading-queue … stop-settles-the-in-flight-play) and the tests that enforce them. The two that catch the most mistakes:

  • core-never-re-grows-a-reading-queue — core must never re-grow a Reading Queue. New capability goes in the plugin's ui/.
  • marketplace-only-never-bundled — the bundle-exclude must stay declared in all three mirrored sites, or the plugin ships inside the app bundle.

There is no unit harness for the webview DOM; verify UI behavior by hand in the dev instance. The pure logic and data layer are covered by tests/unit/plugins/reading-queue/.

Related

plugin-marketplace.md covers the Marketplace this plugin is installed from and the sandbox model its UI runs inside, while plugin-bridge-capabilities.md documents the host capabilities a sandboxed plugin may reach — the AgentMC namespaces this plugin's data, summary, HTTP and read-aloud layers are built on. The invariants a change to it must never break, together with the tests that enforce them, are locked in reading-queue-contract.md.

Last verified 2026-09-28