Release Notes
Added
listSessionCatalog({ sources?, limit?, beforeMs?, after? })— the materialized
session catalog, newest first, as one indexed query over thesessionstable.
No provider transcript is opened and neitherhistorynorsession_eventsis
scanned, so it stays fast on first paint with thousands of sessions. Rows come
back asCatalogSession: cwd, git branch, first/last activity, derived
firstPrompt, observedmodels, originator, agent version, repo identity,
sourceStamp, anddiscoveryState('shallow'for a catalog-only row,
'full'once a sync ingested the transcript; aNULLcolumn predates the
catalog and reads as'full').source = 'trajectory'rows are excluded
defensively — trajectories are derived records, not sessions. Ordering is
last_activity_ms DESCwith null timestamps last, thensourceand
session_id— the catalog's total order. Recency alone is not a key: one
discovery pass can stamp many sessions with the same mtime-derived
millisecond, so a cursor that carries only a timestamp drops every row tied
with a page boundary. A configuredprojectScopeconstrains rows bycwd, as
getHandoffdoes, so sources with no working directory (relay) drop out of a
scoped listing. A negativelimitthrows aRangeError— SQLite reads a
negativeLIMITas "unlimited", so it would otherwise dump the whole catalog.
Returns[]in JSONL fallback mode: the fallback scan buildshistoryonly,
and just the native discovery engine writes the catalog.listSessionCatalogPage(options)→{ sessions, nextCursor }— the same
listing plus the cursor that continues it, mirroring the native
list_session_catalog_pageand the CLI'snext_cursor.nextCursoris
aCatalogCursor({ lastActivityMs, source, sessionId }) and is non-null
only when the page filled its limit, so following it untilnullwalks the
whole catalog with no skipped and no repeated rows — even across a page
boundary that lands inside a group of tied timestamps, and through the tail of
rows whose recency is unknown, which stays reachable from a dated cursor.
ListCatalogOptionsgainsafterfor that cursor;beforeMssurvives as a
coarse cutoff and is ignored whenafteris set.- In-memory catalog migration — the catalog columns (
first_prompt,
models_json,originator,agent_version,repo_url,initial_commit,
workspace_roots_json,source_stamp,discovery_state) landed after 0.5.0,
so opening an older database file now backfills each one best-effort with
ALTER TABLE ... ADD COLUMNagainst the in-memory copy sql.js holds — the same
trick already used forhistory.git_branch. Without it every catalog read on
a pre-0.6 file would fail withno such column; with it the missing values
simply read asNULL. The file on disk is never modified. The catalog
indexes are created on the copy too —idx_sessions_source_last,
idx_sessions_raw_path, and the two composite indexes that carry the whole
total order (idx_sessions_recency,idx_sessions_source_recency) — so a
query here plans the way it does natively. Thediscovery_skipstable
(source,locator,stamp, …), which native discovery uses to remember
that a file is not a session, is created for shape parity; the SDK never
reads it. discoverSessions({ sources?, limit?, onSession?, onDiagnostic?, binPath?, env? })
— a top-level async function (not anAiHistmethod) that drives
ai-hist sessions discover --jsonand parses its JSONL stream into
{ sessions, diagnostics, summary }, invokingonSessionprogressively as
rows arrive. It is top-level because discovery writes the on-disk database
while anAiHistis an in-memory snapshot: a method would return a reader that
cannot see the rows it just wrote, so re-open before listing. The limit is
global across providers and applied by recency, matching the native engine.
Binary discovery reusesresolveAiHistBinaryfrom the cloud-push path
($AI_HIST_RUST_BIN→ the install.sh location →ai-histonPATH).
Unparseable or unknown JSONL lines are skipped rather than failing the run, so
a newer binary's extra line types are forward-compatible.DiscoveryError, with the run's own evidence attached. A provider that
fails still only contributes a diagnostic and the run resolves; the promise
rejects when the binary cannot be run (the message namesAI_HIST_RUST_BIN),
when the run exits non-zero because every provider failed, or when the closing
summary is missing or announces a contract version this SDK does not
implement. The native command writes its diagnostics and the summary trailer
even on an all-provider failure, so the error carriesdiagnostics,
summary,stderr, andexitCoderather than an opaque exit status.
CheckingcontractVersionis no longer left to the caller: a mismatch (or an
absent trailer, which the contract makes mandatory) means the rows cannot be
interpreted safely, so it is refused instead of half-parsed.DiscoverResult.summary
is therefore non-optional.- Callback exceptions no longer escape the stream. An error thrown by
onSessionoronDiagnosticused to surface as an unhandled exception inside
the stdout handler, which can take a host process down. It now aborts the run:
the child is killed and the promise rejects with the caller's own error. SESSION_CATALOG_CONTRACT_VERSION(currently1), mirroring the native
constant, plus theCatalogSession,ListCatalogOptions,
DiscoverSessionsOptions,DiscoverResult,DiscoverySummary,
DiscoveryDiagnostic,DiscoveryCounters,ProviderDiscoverySummary, and
SourceExemptiontypes.- MCP tool
list_sessions(read-only) —sources?,limit(default 20,
max 200),before_ms, andafter(the previous reply'snextCursorobject);
returns the catalog rows plusnextCursoras JSON alongside
contractVersion,sourceKind,dbPath, andprojectScope. Itssources
enum omitstrajectory, which the catalog never contains. With no SQLite
database present the tool answers{ sessions: [], nextCursor: null, sourceKind: "none", note }without opening the reader: the catalog only
ever lives in SQLite, so building the JSONL fallback there would walk every
local provider file — seconds of I/O — to produce a guaranteed-empty catalog.
Every other tool keeps the fallback behavior it had.
Changed
listSessionsis unchanged and still derives sessions fromhistory; its doc
comment now points atlistSessionCatalogas the fast and complete path
(the catalog also holds sessions that only shallow discovery has seen).- A cursor missing
sourceorsessionIdthrows aTypeError. A timestamp
alone cannot separate rows that share a millisecond, and quietly ignoring the
half-cursor would restart the walk at page one — the same half-cursor the
native CLI refuses.