Repository navigation
perf(host-core): reduce history costs in search and session reads - #1202
Conversation
|
Review result: do not merge yet. The measured search/session improvements are plausible, and I ran the relevant host-core coverage: There is nevertheless a production data-safety regression in the append path. Before this PR, This is not a cosmetic concern: the PR description itself calls it a reliability change, but no durable acknowledgement/error propagation or retry path replaces the old guarantee. Please preserve the existing durability semantics (or define and validate an explicit new contract) before landing. The PR head is also behind current |
12fea91 to
f98d71c
Compare
a315b0a to
6fae5e8
Compare
|
The durability regression is resolved in the current head: transcript writes are committed before the RPC response, and a failed I am not merging yet because GitHub reports no checks for this PR, while the current diff also changes desktop JS search/session paths. The local desktop typecheck could not be run from the isolated worktree because pnpm rejects the reused symlinked task-state directory. Please provide a CI candidate (or a clean JS build/typecheck/lint/test result) before landing this 12-file performance change. |
|
Found a remaining root-cause durability issue in the current deferred-flush design; please do not merge yet.
The new tests only prove that the deferred request reports the flush error; they do not assert the post-failure DB/transcript state or retry behavior. The fix needs to make the DB/index commit conditional on durable transcript success, or add an explicit durable-pending/retry/reconciliation state that prevents a retry from becoming a no-op until the transcript is synced. |
The Markdown renderer rejects raw Windows drive-letter URLs as unsafe schemes. Encode the resolved path in the inline-code branch, matching the established text-path behavior, and assert that ReactMarkdown emits an anchor around the inline code.
…ing-20261002 fix(host-core): safely inline Read image results
…landing-20261002 fix(desktop): preserve clickable Windows paths in inline code
Rollup emits Main as out/main/index.js plus shared chunks under out/main/chunks/. getModuleDirectory returned the chunk directory, so renderer/preload/plugin-host paths resolved one level too deep and the window loaded a missing index.html (black screen).
Add a regression test proving getModuleDirectory() resolves to out/main from both the entry bundle and out/main/chunks/*.js, so renderer/preload/plugin-host paths cannot drift one level deep again. Document the shared-chunk layout in E2E-001.
Reconcile renderer pending queues with successful Host reads so requests resolved outside the renderer disappear without dropping requests delivered during a read. Guard overlapping restores by generation and cover the lifecycle with regression tests.\n\nRefs: mocode vastsa#484
…-blackscreen fix(electron): resolve main output root from inside Rollup chunks
When an OpenAI-compatible provider configures a canonical model name such as gemini-3.8-flash, relay catalog resolution matches the official Google model entry and attaches api: "google-generative-ai" to its modelConfig. Previously, providerRequestTransport allowed any model-level api to override the provider apiStyle unconditionally, forcing the runtime to route requests through the Google Generative AI adapter against the OpenAI proxy endpoint, causing immediate HTTP 404 errors. Scope model-level wire API overrides to wire-compatible protocols, ensuring that foreign catalog protocols (e.g. Google Generative AI or Anthropic Messages) do not overwrite an OpenAI-compatible provider's configured chat_completions transport. fixes vastsa#1310
The Command-K recents view and the tray still offered a scheduled run's transcript, because only the SessionList and search hits applied the ownership rule: the palette's empty-query list reads the store directly, and the tray builds its recents from `session.list`. Both now go through the same rule — one `listableSessions` helper in the renderer and its main-process twin for the tray — so no session list offers a conversation that belongs to the Scheduled page. Also repairs the spec text an earlier change left truncated (a duplicated English fragment, the Chinese UI/IA paragraph and the config_json field list), records the retention boundary a fast cadence reaches — a pruned run takes its session's ownership marker with it and that transcript returns to the ordinary lists — and indexes `task_runs(session_id)` for the ownership probe that summaries and search run per row. Reported by @vastsa in review of vastsa#1298. refs vastsa#1291
Use the published catalog for chat model limits and capabilities so provider model lists no longer inherit one repeated pi-ai context value. Keep endpoint discovery and explicit per-account user overrides intact. Add regression coverage and update the catalog decision and runtime specs. Fixes vastsa#1304
The reordering left the previous three lines in place, so the step list repeated "fields and verify its first occurrence is one hour away; pause/resume; Run now" and trailed off at "conversation, use model tool calls…". Restores the original "observe automatic completion; delete the settled task." step, which the Chinese plan still states.
Pin the issue's one-million-token Astra configuration through live model discovery, metadata caching, settings selection, and runtime budget resolution. This locks down the distinction between published catalog data and an account's explicit context window.
Keep the Chinese decisions log aligned with the new models.dev chat metadata authority and preserve the superseded catalog decisions as historical context. Match the source decision table and timeline.
…etail feat(scheduled): 定时任务页改造为「任务 + 运行记录」主从视图(vastsa#1291 阶段一)
Update source-contract and lookup fixtures that still encode the former Pi metadata authority. Keep the assertions focused on bundled models.dev metadata and explicit provider-binding resolution.
fix(models): use models.dev as metadata authority
Large chat content could trigger unbounded scans and repeated parser work on the renderer path. Cap synchronous work while keeping complete source available through localized plain-text fallbacks.
fix(renderer): bound transcript rendering work
The TodoDock contract update left a repeated phrase in the component spec.\nRemove the duplicate so the revised behavior reads cleanly in the landing diff.
…list feat(todo): 展开后的 TodoDock 列出全部清单并在 dock 内滚动(vastsa#1319 方案 A)
Keep the header as the native drag owner while limiting no-drag to interactive tabs and existing action controls. This restores window dragging without changing tab selection or reorder behavior.
Coordinate screenshot capture with the pane that owns native bounds so Chromium cannot restore a stale viewport after a concurrent resize. Pin queued captures to their originating page and preserve caller errors.
fix(work-panel): restore window dragging in unused tab-strip space
…ure-resize fix(browser): preserve the latest viewport after screenshot capture
projectSessionGetResult projected only the newest `session.compaction` record.
`session.compactions` is the unbounded compaction history and every entry keeps
its own `summary` / `retainedTail` / `details.modifiedFiles`, so a session that
has been compacted several times still overflowed the 512 KiB `MAX_RESULT_CHARS`
limit and the whole answer was replaced by a `{truncated, reason:
"MCP_RESULT_LIMIT", preview}` envelope. External clients (`pi_session_get`) then
never reached `messages`, and reducing the transcript page could not help
because the overflow is independent of `messageLimit` / `contentLimit`.
Project every history entry to the same compact identity the tools contract
promises (`createdAt` and `details.generation`), and keep the projection working
for sessions that carry history but no newest record. Answers already under the
limit, sessions without any compaction record, and the desktop's own session
detail are unchanged.
Reported from the MoCode client (mocode issue 506).
…on-history fix(mcp): bound the session/get compaction history before truncating
vastsa
left a comment
There was a problem hiding this comment.
Thanks for the careful measurements and the substantial performance work. I re-checked the current head and cannot approve or merge it yet: the deferred flush still leaves a data-safety gap.
sessions::append_message calls append_record, which writes the transcript line and commits the SQLite message index inside the handler. append_line only registers the file for sync_data() in the request-local queue; commit_writes_of runs that queue after the handler has returned. If the flush fails, response_for correctly returns an RPC error, but the SQLite row remains committed. A retry then hits message_indexed() and returns success without rewriting or re-registering the transcript write. A failed device flush can therefore leave SQLite claiming a message whose transcript is not durable, and a later crash can lose that message.
The current a_request_only_answers_after_its_lines_reached_the_device test checks the RPC error, but not the database/transcript state or retry behavior. Please make the index commit conditional on a successful transcript flush (or add an explicit durable-pending/rollback path that prevents retry from becoming a no-op) and add a failing-flush-then-retry regression test that verifies the canonical transcript and index agree.
This head is also behind current main (PR base 4dee458, current origin/main c1afefe), and GitHub reports no checks for it. After correcting the persistence ordering, please refresh it from current main and provide the required green integration checks. The performance direction is valuable; these are landing blockers, not requests to redo the measurements.
The legacy Edit shape (`old_string` / `new_string` without `tag` / `ops`)
located the unique match by byte offset but then emitted a whole-line
`PUT first.=last:` with `new_string` as the body. `PUT N.=M` replaces whole
lines, so when `old_string` matched only part of a line the unmatched prefix
of the first line and suffix of the last line were silently deleted while the
tool still reported success (`let x = foo;` with `= foo;` -> `= bar;` became
`= bar;`). Counting `old_string.split('\n')` lines also overshot by one when
`old_string` ended in a newline, so deleting `foo\n` wiped the next line too.
Build the exact substring result instead, trim the unchanged leading and
trailing lines, and lower the difference to a single line-anchored op
(`PUT a.=b:`, `CUT a.=b`, or a `PUT >N:` insertion) that still goes through
`hashline::apply_edit` with the live tag. Only changed lines are anchored, so
the provenance gate sees exactly what changed. Exact whole-line matches produce
the same file as before; an identical replacement still fails with
`EDIT_NO_CHANGE`, and not-found / ambiguous matches still fail with
`EDIT_LEGACY_MATCH_FAILED`.
Refs vastsa#1106
Give skill invocations an explicit namespace so they can be distinguished from ordinary slash commands while retaining the existing skill IDs and send-time resolution.
The new Skill namespace left runtime specs and a provider acceptance scenario describing the old syntax. Align those contracts and exercise catalog generation, completion text, multiple mentions, and send-time ID resolution with a permanent regression test.
…refix feat(composer): Skill 命令统一使用 /skill: 前缀
The OpenAI Realtime adapter required an HTTPS base URL and the Live WebSocket transport accepted only wss, so a Realtime-compatible server on the user's own machine (http://127.0.0.1:8010/v1) could not be used. ADR 0304 already lets endpoints the user typed reach loopback and LAN over plain http under the default relaxed network mode; the Realtime path was the one user-supplied endpoint still hard-coded to TLS. Map an http base URL to ws and let the existing public-network guard decide, from networkPolicy.mode, whether that plaintext hop is allowed. Third-party Live endpoints (Gemini) keep wss only, base URLs with credentials, a query or a fragment stay refused, and a plain ws endpoint on a proxied route fails closed rather than being tunneled in the clear. The scheme rules move to a small Electron-free module so they can be tested directly. Refs vastsa#1318
The line-anchored edit format preserves terminal newline state, so the legacy substring adapter cannot express a terminal-newline-only change. Pin the result in a regression test and make the compatibility contract explicit.
…al-line fix(tools): preserve unmatched text in partial-line legacy edits
Exercise the production transport against a loopback WebSocket and keep the proxied plaintext refusal covered. Add a plain-HTTP fixture mode so the full isolated call journey can validate the user-endpoint policy.
…k-http fix(live-voice): let Realtime use a plain-HTTP user endpoint
Transcript content is the source of truth for indexed messages. Sync a line before committing its SQLite row so a failed flush cannot leave a retry with a phantom index. This integration keeps the contributor commits and merges the latest main, including both migration index changes.
vastsa
left a comment
There was a problem hiding this comment.
Thanks for the careful performance work. I verified the original durability issue and added a failing-flush-then-retry regression test. The transcript sync now completes before the derived SQLite transaction, so a failed sync cannot leave the index claiming a transcript line that was not persisted; the retry writes the line exactly once.
The append-throughput optimization and its benchmark claim have been removed from this PR because the deferred-flush design could not preserve that durability ordering. The remaining search and session-read improvements are retained. The PR integration merge ref (a701f65, tree df4c0ab) has the same executable tree as the validated task candidate.
Validation: host-core tests 779/779, workspace tests passed (desktop 3556/3556), E2E 23/23 with 2 API-key-dependent skips, changed desktop session tests 27/27, build, lint, formatting, and architecture checks passed. Desktop typecheck still reports existing LucideProps icon prop errors, reproduced on current main; the changed lines do not overlap them. Approving and merging.
Problem
Four costs that grow with session history. Each was measured before the change
and again with the change ablated.
path — four call sites, one of them twice per call.
messagescarriedonly the
UNIQUE (session_id, seq)index the write path needs, while eachstatement ranks by
created_atand cuts with aLIMIT, soEXPLAIN QUERY PLANreportsUSE TEMP B-TREE FOR ORDER BYon the uncappedLIKE branch, both trigram branches and the per-session snippet lookup.
snippet()also ran once per FTS match rather than once per returned row.(
transcript_contains_id,read_tool_call).offset / 30costoffset / 30 + 1host calls, each paying the samewhole-table aggregation, because
OFFSETdoes not change it.Result
search.queryuncapped LIKE, first 20 of 40ksearch.querytrigram, 40ktranscript_contains_id/read_tool_call, 21 MBMedians of five runs on one fixture per row. The ablation for the search indexes
is the same statements with the indexes dropped again, so the difference is
attributable to the index rather than to the statement text.
Implementation notes
idx_turns_ended_atarrives: an idempotentCREATE INDEX IF NOT EXISTSinboot_maintenanceplus the schemadeclaration. No row is rewritten, no schema-version bump, no migration backup.
The first launch after the upgrade builds both indexes once.
snippet()replacement repeatsMATCH ?1in its subquery. An FTS5auxiliary function reads the phrase list of the MATCH that positioned its
cursor, so reaching a document by rowid alone returns the head of the document
instead of the window around the hit. That faster form was rejected after a
row-for-row comparison found 0 of 20 snippets identical; the shipped form is
20 of 20.
update_messagestays O(history) deliberately: appending the replacement is396x cheaper and was rejected because read-path windows are physical line
coordinates, so a search jump to a stamped message would open on the tail of
the session.
sync_data()before the deriveddatabase transaction. The append-throughput optimization is withdrawn pending
a transaction-safe durability design; no append-speed result is claimed here.
Verification
cargo fmt --all -- --check— passed.cargo clippy -p host-core --all-targets --locked— passed with twonon-blocking warnings in transcript cache/read code.
cargo test -p host-core --locked— 779 passed, 0 failed.cargo build -p host-core --locked— passed.ELECTRON_MAJOR_VER=43 pnpm build:js— passed.pnpm --filter @pi-desktop/desktop typecheck— fails on existingLucidePropsicon prop errors; the same class of errors reproduces oncurrent
main. The changed PR lines do not overlap those errors.pnpm lint— passed.pnpm -r --if-present test— passed across all workspaces, includingdesktop 3556/3556.
pnpm test:e2e— 23/23 passed, 2 skipped because no API key was set.pnpm check:pr-base— passed; latestorigin/mainis an ancestor of this PR head.
Compatibility, migration, security
search.sessionsgains anoptional
limit; omitted, it keeps the existing 30-row cursor contract.sync returns an error. No append durability contract is weakened. The
append-speed optimization and its benchmark claim were removed.
changes.
search tests plus the desktop search-merge expectations cover it; the latter
are updated to pin the new request shape.
13 files changed, all modified — none added, none deleted.