Closes#80268
For Chrome MCP existing-session profiles, browser status previously
exposed only transport-handshake fields (cdpHttp, cdpReady) sourced
from isTransportAvailable(...). It did not surface whether a
page-level tool round-trip (list_pages, etc.) actually succeeds, so
operators and downstream tooling had no honest signal to distinguish
"transport handshake passed" from "page tools are usable".
This adds a pageReady field to BrowserStatus, derived from
profileCtx.isReachable(...) for chrome-mcp profiles (with a status-
bound 5s timeout) and mirroring cdpReady for managed CDP profiles
where the WS handshake already covers page-level reachability.
The status route opts the page probe into ephemeral mode so a passive
status call does not seed a persistent cached Chrome MCP session as a
side effect. listChromeMcpTabs reuses an existing cached attach
session if one already exists, otherwise opens a temporary session
that is closed immediately after the round-trip. The cached-session
path used by /tabs and other interactive routes is unchanged.
isReachable now threads the new ephemeral option (alongside timeoutMs)
into listChromeMcpTabs; existing callers in tabs.ts pass only
timeoutMs and continue to use the cached path.
The page probe is skipped when transport itself is down so status
latency does not regress on offline profiles.
Test changes:
- basic.existing-session.test.ts: the prior assertion that running:
true and cdpReady: true with isReachable: false is now flipped to
assert pageReady: false in that state, matching the new contract.
- New tests cover: probe-throws (treated as page-down), both-succeed
(pageReady: true), transport-down (probe skipped, pageReady: false),
and an ephemeral-mode regression that asserts the status probe
passes { ephemeral: true } so it cannot seed a cached session.
The remaining whole-file transcript scans flagged by ClawSweeper triage on
splitting on newlines. On long-running sessions where transcripts grow into
the multi-MB / 100s-of-MB range that scales peak RSS with file size and is
the practical OOM risk in the report.
Add a shared streaming helper module `src/config/sessions/transcript-stream.ts`
exposing:
- `streamSessionTranscriptLines(filePath, { signal? })`: forward async-iterator
over trimmed non-empty lines using `fs.createReadStream` + `readline` with
`crlfDelay: Infinity`. Bounded to one line of memory at a time and honours
an abort signal between lines.
- `readSessionTranscriptTailLines(filePath, { maxBytes? })`: tail-only read of
the last `maxBytes` of a file (default 4 MiB, clamped to [1 KiB, 64 MiB]),
returning trimmed non-empty lines in reverse order. Drops the leading line
of the slice when the window does not start at byte zero so callers never
see a partial-line suffix.
Migrate every flagged whole-file scan to these helpers while preserving the
malformed-line tolerance and idempotency-key return semantics callers depend
on (see `Remaining risk / open question` on the issue):
- `src/config/sessions/transcript.ts`: `readLatestAssistantTextFromSessionTranscript`,
`readTailAssistantTextFromSessionTranscript`, and the delivery-mirror dedupe
helper `findLatestEquivalentAssistantMessageId` now use the tail helper;
`transcriptHasIdempotencyKey` uses the forward stream helper.
- `src/gateway/server-methods/chat.ts`: the inline `transcriptHasIdempotencyKey`
used by chat-method append idempotency now uses the forward stream helper
and tolerates malformed lines mid-scan (matching the sibling helper in
`config/sessions/transcript.ts`).
- `src/gateway/session-compaction-checkpoints.ts`: `readTranscriptEntriesForForkAsync`
builds the fork entry array from the forward stream helper instead of one
big `fileHandle.readFile("utf-8")` call.
Fixes#54296.
* fix(doctor): warn when per-agent model omits fallbacks key and defaults chain is non-empty
`resolveAgentModelFallbacksOverride` in `src/agents/agent-scope.ts` returns
`[]` (no fallbacks) when a per-agent model is configured without an explicit
`fallbacks` key. At runtime this silently clobbers
`agents.defaults.model.fallbacks`, leaving the agent with no fallbacks.
Two config patterns hit this:
1. String form: `"model": "openai/gpt-5.5"` — user likely means "use this model,
inherit fallbacks from defaults".
2. Object without `fallbacks` key: `"model": { "primary": "openai/gpt-5.5" }` —
user likely means "just set the primary, keep defaults fallbacks".
The only explicit "no fallbacks" signal is `fallbacks: []`. This change adds
`collectImplicitFallbackClobberWarnings` / `noteImplicitFallbackClobberWarnings`
to the doctor config-analysis flow, which warns on either ambiguous shape only
when `agents.defaults.model.fallbacks` is non-empty (so there is something to
clobber).
No semantic change to how fallbacks resolve; this is a doctor-only diagnostic.
Closes#79369. Extends the scope of closed PR #79389 (string-form only) to
also cover the object-without-fallbacks case.
* fix(doctor): mirror runtime model primary normalization
* fix(doctor): strengthen fallback warning proof
* fix(doctor): tolerate malformed agent lists
* fix(doctor): type guarded agent runtime policy
* fix(ui): sync quick settings i18n baseline
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>