From 3a73bcef9a909121e714d4956a846574a82105b4 Mon Sep 17 00:00:00 2001 From: muzimu217 Date: Mon, 19 Oct 2026 21:39:33 -0700 Subject: [PATCH 1/3] feat(host): workspace index infrastructure behind an opt-in switch Squashed rebuild of #697 onto current main. Content unchanged from the reviewed branch; only conflict resolutions differ: - package.json: keep both the new test:e2e:index script and main's newer test:e2e:jev/storage entries. - shared/types.ts: export both worktree.js and workspace-index.js. - rpc/mod.rs: keep main's response_for and this branch's checked_index_root, plus both import lists. - settings-ia docs (en/zh): take main's current wording (Experimental destinations available in all builds) and add Index to the Workspace group. - e2e test plan: keep the new E2E-INDEX rows and main's E2E-263 row. --- apps/desktop/electron/main/ipc/session-ipc.ts | 16 + .../src/components/settings/IndexPage.tsx | 344 +++++++ .../src/components/settings/MetricTile.tsx | 38 + .../src/features/settings/SettingsPage.tsx | 6 + apps/desktop/src/lib/api.ts | 14 + apps/desktop/src/lib/settings-search.ts | 19 + apps/desktop/src/styles/settings.css | 280 ++++- apps/desktop/src/styles/tokens.css | 35 +- apps/desktop/test/index-settings.test.mjs | 134 +++ apps/desktop/test/settings-general.test.mjs | 2 + crates/host-core/src/index.rs | 962 ++++++++++++++++++ crates/host-core/src/index/fts.rs | 81 ++ crates/host-core/src/main.rs | 1 + crates/host-core/src/rpc/mod.rs | 378 ++++++- crates/host-core/src/state.rs | 22 + crates/host-core/src/tools/ignore_rules.rs | 122 +++ docs/spec/03-runtime/01-ipc-protocol.md | 15 + docs/spec/03-runtime/04-data-storage.md | 13 + docs/spec/03-runtime/06-host-rpc-protocol.md | 18 + docs/spec/03-runtime/08-error-codes.md | 3 + docs/spec/04-ux/06-settings-ia.md | 35 +- docs/spec/06-delivery/04-e2e-test-plan.md | 62 +- docs/zh-CN/spec/03-runtime/01-ipc-protocol.md | 13 + docs/zh-CN/spec/03-runtime/04-data-storage.md | 10 + .../spec/03-runtime/06-host-rpc-protocol.md | 13 + docs/zh-CN/spec/03-runtime/08-error-codes.md | 3 + docs/zh-CN/spec/04-ux/06-settings-ia.md | 27 +- .../spec/06-delivery/04-e2e-test-plan.md | 43 + package.json | 1 + packages/i18n/src/locales/de/index.ts | 50 +- packages/i18n/src/locales/en/index.ts | 47 + packages/i18n/src/locales/es/index.ts | 50 +- packages/i18n/src/locales/fr/index.ts | 50 +- packages/i18n/src/locales/ko/index.ts | 47 + packages/i18n/src/locales/pt-BR/index.ts | 47 + packages/i18n/src/locales/tr/index.ts | 47 + packages/i18n/src/locales/zh-CN/index.ts | 47 + packages/i18n/src/locales/zh-TW/index.ts | 47 + packages/shared/src/errors.ts | 6 + packages/shared/src/protocol.ts | 3 + packages/shared/src/types.ts | 1 + packages/shared/src/types/settings.ts | 6 + packages/shared/src/types/workspace-index.ts | 26 + scripts/README.md | 1 + scripts/dev-electron.mjs | 7 +- scripts/e2e-index.mjs | 123 +++ 46 files changed, 3294 insertions(+), 21 deletions(-) create mode 100644 apps/desktop/src/components/settings/IndexPage.tsx create mode 100644 apps/desktop/src/components/settings/MetricTile.tsx create mode 100644 apps/desktop/test/index-settings.test.mjs create mode 100644 crates/host-core/src/index.rs create mode 100644 crates/host-core/src/index/fts.rs create mode 100644 packages/shared/src/types/workspace-index.ts create mode 100644 scripts/e2e-index.mjs diff --git a/apps/desktop/electron/main/ipc/session-ipc.ts b/apps/desktop/electron/main/ipc/session-ipc.ts index 31cc924cd7..a68d33777c 100644 --- a/apps/desktop/electron/main/ipc/session-ipc.ts +++ b/apps/desktop/electron/main/ipc/session-ipc.ts @@ -310,6 +310,22 @@ export function registerSessionIpc({ }; }, ); + + handle(IPC.invoke.indexStatus, async (input?: { rootPath?: string }) => { + if (!host) throw Object.assign(new Error("host unavailable"), { errorCode: "HOST_UNAVAILABLE" }); + return host.call("index.status", input ?? {}); + }); + + handle(IPC.invoke.indexRebuild, async (input?: { rootPath?: string }) => { + if (!host) throw Object.assign(new Error("host unavailable"), { errorCode: "HOST_UNAVAILABLE" }); + return host.call("index.rebuild", input ?? {}); + }); + + handle(IPC.invoke.indexClear, async (input?: { rootPath?: string }) => { + if (!host) throw Object.assign(new Error("host unavailable"), { errorCode: "HOST_UNAVAILABLE" }); + return host.call("index.clear", input ?? {}); + }); + handle( IPC.invoke.sessionGet, async ( diff --git a/apps/desktop/src/components/settings/IndexPage.tsx b/apps/desktop/src/components/settings/IndexPage.tsx new file mode 100644 index 0000000000..1f07f946ff --- /dev/null +++ b/apps/desktop/src/components/settings/IndexPage.tsx @@ -0,0 +1,344 @@ +import { useCallback, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import type { AppSettings, WorkspaceIndexRoot } from "@pi-desktop/shared"; +import { api } from "../../lib/api"; +import { Button, cx } from "../ui"; +import { MetricTile } from "./MetricTile"; +import { + IconActivity, + IconDatabase, + IconFileText, + IconRefresh, +} from "../icons"; + +type IndexPageProps = { + settings: AppSettings; + saveSettings: (patch: Partial) => Promise; +}; + +type LoadState = + | { kind: "loading" } + | { kind: "error" } + | { kind: "ready"; root: WorkspaceIndexRoot | null }; + +/** localStorage flag for the one-time local-only nudge under the index page. */ +const NUDGE_DISMISSED_KEY = "pi.index.nudgeDismissed.v1"; + +function formatBytes(bytes: number): string { + if (bytes <= 0) return "0 B"; + const units = ["B", "KB", "MB", "GB"]; + const exponent = Math.min( + units.length - 1, + Math.floor(Math.log(bytes) / Math.log(1024)), + ); + const value = bytes / 1024 ** exponent; + return `${value >= 100 || exponent === 0 ? Math.round(value) : value.toFixed(1)} ${units[exponent]}`; +} + +function formatRelative(updatedAt: number): string { + if (updatedAt <= 0) return "—"; + const deltaSeconds = Math.max(0, Math.round((Date.now() - updatedAt) / 1000)); + if (deltaSeconds < 60) return `${deltaSeconds}s`; + if (deltaSeconds < 3600) return `${Math.floor(deltaSeconds / 60)}m`; + if (deltaSeconds < 86400) return `${Math.floor(deltaSeconds / 3600)}h`; + return `${Math.floor(deltaSeconds / 86400)}d`; +} + +const STATUS_TONE: Record = { + fresh: "ok", + building: "busy", + stale: "warn", + failed: "error", + partial: "warn", + disabled: "", + skipped_over_limit: "warn", +}; + +export function IndexPage({ settings, saveSettings }: IndexPageProps) { + const { t } = useTranslation(); + const [state, setState] = useState({ kind: "loading" }); + const [busy, setBusy] = useState<"rebuild" | "clear" | null>(null); + const [actionError, setActionError] = useState(false); + // One-time note: once dismissed it stays dismissed (localStorage), matching + // the audit's "never render again once written" requirement. + const [nudgeVisible, setNudgeVisible] = useState(() => { + try { + return localStorage.getItem(NUDGE_DISMISSED_KEY) !== "1"; + } catch { + return true; + } + }); + const grepBoost = settings.indexGrepBoost === true; + const building = state.kind === "ready" && state.root?.status === "building"; + + const dismissNudge = () => { + try { + localStorage.setItem(NUDGE_DISMISSED_KEY, "1"); + } catch { + // Storage unavailable (e.g. locked-down profile): degrade to a + // session-only dismiss rather than blocking the control. + } + setNudgeVisible(false); + }; + + const refresh = useCallback(async () => { + setState((current) => + current.kind === "ready" ? { kind: "ready", root: current.root } : { kind: "loading" }, + ); + try { + const result = await api.indexStatus(); + setState({ kind: "ready", root: result.roots[0] ?? null }); + } catch { + setState({ kind: "error" }); + } + }, []); + + useEffect(() => { + void refresh(); + }, [refresh]); + + // While a background rebuild is in flight, poll once a second — paused + // whenever the window is hidden, and torn down with the page. + useEffect(() => { + if (!building) return; + const poll = () => { + if (document.visibilityState !== "visible") return; + void api.indexStatus().then((result) => { + const next = result.roots[0]; + if (next) setState({ kind: "ready", root: next }); + }); + }; + const timer = setInterval(poll, 1000); + return () => clearInterval(timer); + }, [building]); + + const rebuild = async () => { + if (busy) return; + setBusy("rebuild"); + setActionError(false); + try { + const result = await api.indexRebuild(); + setState({ kind: "ready", root: result.root }); + } catch { + setActionError(true); + void refresh(); + } finally { + setBusy(null); + } + }; + + const clear = async () => { + if (busy) return; + setBusy("clear"); + setActionError(false); + try { + await api.indexClear(); + setState({ kind: "ready", root: null }); + } catch { + setActionError(true); + void refresh(); + } finally { + setBusy(null); + } + }; + + if (state.kind === "loading") { + return ( +
+ {t("index.loading")} +
+ ); + } + + if (state.kind === "error") { + return ( +
+
+
+
+
+
{t("index.loadErrorTitle")}
+
{t("index.loadErrorDesc")}
+
+
+ +
+
+
+
+
+ ); + } + + const { root } = state; + const progress = root?.status === "building" ? root.progress : undefined; + const progressPct = + progress && progress.filesTotal > 0 + ? Math.min(100, Math.max(0, Math.round((progress.filesDone / progress.filesTotal) * 100))) + : 0; + + return ( +
+ {/* Sits directly under the page title the settings shell renders. */} +

{t("index.indexSubtitle")}

+ +
+

{t("index.card.health")}

+
+ {building ? ( +
+ {progress ? ( + <> +
+ ) : null} + {root ? ( +
+ } + tone={root.status === "fresh" ? "success" : root.status === "failed" ? "danger" : "warning"} + label={t("index.card.status")} + value={ + + {t(`index.status.${root.status}`)} + + } + caption={t("index.statusDesc")} + /> + } + tone="accent" + label={t("index.card.files")} + value={root.fileCount.toLocaleString()} + badge={ + root.errorCount > 0 ? ( + + {t("index.card.errors")}: {root.errorCount} + + ) : undefined + } + /> + } + tone="accent" + label={t("index.card.size")} + value={formatBytes(root.indexedBytes)} + /> + } + tone="accent" + label={t("index.card.updated")} + value={formatRelative(root.updatedAt)} + /> +
+ ) : ( +
+
+
{t("index.emptyTitle")}
+
{t("index.emptyDesc")}
+
+
+ )} + {root && root.errorCount > 0 && root.lastError ? ( +
+ {root.lastError} +
+ ) : null} +
+
+
{t("index.actions")}
+
+ {t("index.actionsDesc")} {t("index.localOnly")} +
+
+
+ + +
+
+ {actionError ? ( +
+ {t("index.actionError")} +
+ ) : null} +
+
+ + {/* + The behaviour switch gets its own "Codebase" section: it is a setting, + not health telemetry, and the audit flagged it sitting wordlessly + inside the health card. + */} +
+

{t("index.sectionCode")}

+
+
+
+
+ + {t("index.grepBoost")} +
+
{t("index.grepBoostDesc")}
+
+
+ +
+
+
+
+ + {nudgeVisible ? ( +
+ {t("index.nudgeText")} + +
+ ) : null} +
+ ); +} diff --git a/apps/desktop/src/components/settings/MetricTile.tsx b/apps/desktop/src/components/settings/MetricTile.tsx new file mode 100644 index 0000000000..4b59b9d353 --- /dev/null +++ b/apps/desktop/src/components/settings/MetricTile.tsx @@ -0,0 +1,38 @@ +import type { ReactNode } from "react"; +import { cx } from "../ui"; + +export type MetricTone = "accent" | "success" | "warning" | "danger"; + +/** + * Shared metric tile for the index library destination: a tinted icon chip, + * a muted label, a large tabular value, and an optional badge or caption. + */ +export function MetricTile({ + icon, + tone = "accent", + label, + value, + caption, + badge, +}: { + icon: ReactNode; + tone?: MetricTone; + label: string; + value: ReactNode; + caption?: ReactNode; + badge?: ReactNode; +}) { + return ( +
+
+ + {label} + {badge} +
+
{value}
+ {caption ?
{caption}
: null} +
+ ); +} diff --git a/apps/desktop/src/features/settings/SettingsPage.tsx b/apps/desktop/src/features/settings/SettingsPage.tsx index f275f99ef8..38008ea02a 100644 --- a/apps/desktop/src/features/settings/SettingsPage.tsx +++ b/apps/desktop/src/features/settings/SettingsPage.tsx @@ -17,6 +17,7 @@ import { import { pluginViewIcon } from "../../lib/plugin-view-icons"; import { IconArchive, + IconDatabase, IconBookOpen, IconBot, IconChevronLeft, @@ -33,6 +34,7 @@ import { IconMic, } from "../../components/icons"; import { Badge, Button, cx, SegmentedControl, SettingsToggle } from "../../components/ui"; +import { IndexPage } from "../../components/settings/IndexPage"; import { ModelConfigPage } from "../../components/settings/ModelConfigPage"; import { KeyboardShortcutsSection } from "../../components/settings/KeyboardShortcutsSection"; import { FontFamilyRow } from "../../components/settings/FontFamilyRow"; @@ -239,6 +241,7 @@ export function SettingsPage() { mcp: , subagents: , projects: , + index: , sync: , remoteHosts: , voice: , @@ -616,6 +619,9 @@ export function SettingsPage() { {tab === "projects" && } + {tab === "index" && settings && ( + + )} {tab === "sync" && !tabHidden && } {tab === "remoteHosts" && !tabHidden && } diff --git a/apps/desktop/src/lib/api.ts b/apps/desktop/src/lib/api.ts index 85810d88a6..7ed10b072c 100644 --- a/apps/desktop/src/lib/api.ts +++ b/apps/desktop/src/lib/api.ts @@ -1663,6 +1663,20 @@ export const api = { IPC.invoke.statsGetTokenUsageHistory, query, ), + indexStatus: (rootPath?: string) => + invoke<{ roots: import("@pi-desktop/shared").WorkspaceIndexRoot[] }>( + IPC.invoke.indexStatus, + { rootPath }, + ), + indexRebuild: (rootPath?: string) => + invoke<{ root: import("@pi-desktop/shared").WorkspaceIndexRoot }>( + IPC.invoke.indexRebuild, + { rootPath }, + ), + indexClear: (rootPath?: string) => + invoke<{ ok: boolean; cleared: number }>(IPC.invoke.indexClear, { + rootPath, + }), menuRendererReady: () => invoke<{ ready: boolean }>(IPC.invoke.menuRendererReady), setTraySessionPreferences: (preferences: TraySessionPreferences) => diff --git a/apps/desktop/src/lib/settings-search.ts b/apps/desktop/src/lib/settings-search.ts index caed70c213..73fed2a88b 100644 --- a/apps/desktop/src/lib/settings-search.ts +++ b/apps/desktop/src/lib/settings-search.ts @@ -5,6 +5,9 @@ * like "主题" or "theme" can surface the tab that owns the row. */ +/** + * Settings destinations. + */ export type SettingsTabId = | "general" | "ai" @@ -15,6 +18,7 @@ export type SettingsTabId = | "mcp" | "subagents" | "projects" + | "index" | "sync" | "remoteHosts" | "voice" @@ -269,6 +273,21 @@ export const SETTINGS_NAV: SettingsNavEntry[] = [ "project.delete", ], }, + { + id: "index", + labelKey: "settings.nav.index", + titleKey: "settings.index", + // Host/workspace lifecycle — the switch, its status and the rebuild/clear + // actions — so it joins the Workspace group instead of a group of its own. + group: "workspace", + keywordKeys: [ + "index.card.health", + "index.card.files", + "index.card.size", + "index.action.rebuild", + "index.action.clear", + ], + }, { id: "sync", labelKey: "settings.nav.sync", diff --git a/apps/desktop/src/styles/settings.css b/apps/desktop/src/styles/settings.css index 0787181d1e..da52ba0769 100644 --- a/apps/desktop/src/styles/settings.css +++ b/apps/desktop/src/styles/settings.css @@ -2551,13 +2551,20 @@ background: currentcolor; } +/* + State tints use one 14% mix, the repo-wide standard (`.idx-chip`, + `.idx-badge`, `.agent-capability-glyph`, and the other state pills all sit at + 14%). These three badge rules were the only 13% holdouts, so a ready/failed + capability badge read one step dimmer than the identical state on a glyph or + a metric badge. +*/ .agent-capability-badge.is-ready { - background: color-mix(in oklab, var(--ds-success) 13%, transparent); + background: color-mix(in oklab, var(--ds-success) 14%, transparent); color: var(--ds-success); } .agent-capability-badge.is-connecting { - background: color-mix(in oklab, var(--ds-warning) 13%, transparent); + background: color-mix(in oklab, var(--ds-warning) 14%, transparent); color: var(--ds-warning); } @@ -2566,7 +2573,7 @@ } .agent-capability-badge.is-failed { - background: color-mix(in oklab, var(--ds-error) 13%, transparent); + background: color-mix(in oklab, var(--ds-error) 14%, transparent); color: var(--ds-error); } @@ -3026,6 +3033,186 @@ width: 100%; } +/* Index library settings page */ +.idx-state { + color: var(--ds-text-secondary); +} + +.idx-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: var(--space-3); +} + +/* + Metric tile surface, unified with the settings card language (D297): the same + translucent `--ds-tile` fill that `.settings-panel` and `.settings-row` use, + plus one hairline so a 4-up grid of tiles keeps a readable edge on the dark + plate where a 3.5% fill alone can wash out. + + Radius moves off the compact-control rung (`--radius-xs`, 8px) onto + `--radius-md-plus` (14px). That is the documented "cards" rung and it is the + rung `.settings-row` already uses, so the two-tier system is now: structural + panels at `--radius-lg` (16px), cards at 14px. The tile is a card, not a + panel, so 14px is the consistent choice. +*/ +.idx-tile { + display: flex; + flex-direction: column; + gap: var(--space-2); + padding: var(--space-3); + border-radius: var(--radius-md-plus); + background: var(--ds-tile); + /* + `--ds-border-default` rather than `--ds-border-subtle`: the tile fill sits + only ~1.09:1 over the page plate, so the hairline is what actually reads as + the card edge. The mockup's border carries similar weight. + */ + border: 1px solid var(--ds-border-default); +} + +.idx-tile-head { + display: flex; + align-items: center; + gap: var(--space-2); + min-width: 0; +} + +.idx-chip { + display: inline-flex; + align-items: center; + justify-content: center; + width: 26px; + height: 26px; + flex: none; + border-radius: var(--radius-2xs); +} + +.idx-chip-accent { + background: color-mix(in oklab, var(--ds-accent) 14%, transparent); + color: var(--ds-accent); +} + +.idx-chip-success { + background: color-mix(in oklab, var(--ds-success) 14%, transparent); + color: var(--ds-success); +} + +.idx-chip-warning { + background: color-mix(in oklab, var(--ds-warning) 14%, transparent); + color: var(--ds-warning); +} + +.idx-chip-danger { + background: color-mix(in oklab, var(--ds-error) 14%, transparent); + color: var(--ds-error); +} + +.idx-tile-label { + color: var(--ds-text-secondary); + font-size: var(--text-sm); + overflow: hidden; + /* A wrapped two-line label reads better than a truncated one when a badge + squeezes the head row (e.g. en "Current streak" at narrow widths). */ + white-space: normal; +} + +.idx-tile-value { + color: var(--ds-text-primary); + font-size: var(--text-2xl); + line-height: var(--leading-compact); + font-variant-numeric: tabular-nums; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.idx-tile-caption { + color: var(--ds-text-secondary); + font-size: var(--text-xs); + line-height: var(--leading-normal); +} + +/* + Badges sit in a 26px header row next to the tile label. Without `nowrap` a + longer localized label (e.g. the "highest/record/on track" badges, or an + error count) wraps to a second line and pushes the 26px row taller, breaking + the row rhythm across the grid. +*/ +.idx-badge { + display: inline-flex; + align-items: center; + margin-left: auto; + padding: 1px 8px; + border-radius: var(--radius-full); + font-size: var(--text-2xs); + line-height: var(--leading-normal); + white-space: nowrap; + background: color-mix(in oklab, var(--ds-text-primary) 10%, transparent); + color: var(--ds-text-secondary); +} + +.idx-badge-warn { + background: color-mix(in oklab, var(--ds-warning) 14%, transparent); + color: var(--ds-warning); +} + +.idx-badge-ok { + background: color-mix(in oklab, var(--ds-success) 14%, transparent); + color: var(--ds-success); +} + +.idx-status { + display: inline-flex; + align-items: center; + padding: 2px 10px; + border-radius: var(--radius-full); + font-size: var(--text-sm); + line-height: var(--leading-normal); + white-space: nowrap; + background: color-mix(in oklab, var(--ds-text-primary) 10%, transparent); +} + +.idx-status.ok { + background: color-mix(in oklab, var(--ds-success) 14%, transparent); + color: var(--ds-success); +} + +.idx-status.busy { + background: color-mix(in oklab, var(--ds-accent) 14%, transparent); + color: var(--ds-accent); +} + +.idx-status.warn { + background: color-mix(in oklab, var(--ds-warning) 14%, transparent); + color: var(--ds-warning); +} + +.idx-status.error { + background: color-mix(in oklab, var(--ds-error) 14%, transparent); + color: var(--ds-error); +} + +.idx-actions { + display: flex; + gap: 8px; + flex-wrap: wrap; + justify-content: flex-end; +} + +.idx-error-line { + margin-top: var(--space-2); + color: var(--ds-error); + font-size: var(--text-sm); + line-height: var(--leading-normal); +} + +.idx-subtitle { + color: var(--ds-text-secondary); + font-size: var(--text-sm); + margin-top: calc(var(--space-2) * -1); +} + /* Plaintext opt-in for endpoints the user typed themselves: the checkbox sits in the row's control column, and the risk copy arrives only while it is on. @@ -3125,10 +3312,95 @@ } .agent-extension-diagnostic-message { - overflow: hidden; + overflow: hidden; text-overflow: ellipsis; } +/* + Build progress row at the top of the index health card: thin determinate bar + + file counter + a quiet "you can keep working" note. +*/ +.idx-progress { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: var(--space-2); + margin-bottom: var(--space-2); +} + +.idx-progress-bar { + display: block; + flex: 1 1 120px; + min-width: 120px; + height: 4px; + border-radius: var(--radius-full); + background: color-mix(in oklab, var(--ds-text-primary) 10%, transparent); + overflow: hidden; +} + +.idx-progress-fill { + display: block; + height: 100%; + border-radius: var(--radius-full); + background: var(--ds-accent); + transition: width var(--motion-duration-normal) var(--motion-ease-out); +} + +.idx-progress-count { + color: var(--ds-text-primary); + font-variant-numeric: tabular-nums; + white-space: nowrap; +} + +.idx-progress-note { + color: var(--ds-text-secondary); + font-size: var(--text-sm); +} + +/* Icon chip + title line for the codebase switch rows (MetricTile head voice). */ +.idx-row-title { + display: flex; + align-items: center; + gap: var(--space-2); + min-width: 0; +} + +/* + One-time local-only note at the bottom of the index page. Same soft-tile + treatment as the trust notice above; `role="note"` keeps it out of the + critical path, so muted ink and a ghost dismiss. +*/ +.idx-nudge { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: var(--space-2); + padding: var(--space-2) var(--space-3); + border-radius: var(--radius-md); + background: var(--ds-tile); + color: var(--ds-text-muted); + font-size: var(--text-sm); +} + +.idx-nudge-text { + min-width: 0; +} + +@media (max-width: 900px) { + /* + Index tiles reflow on their own (`auto-fit`/`minmax(200px, 1fr)`); only the + action row needs help. At ≤820px `.settings-row` stacks and makes its + control full-width and left-aligned, but `.idx-actions` (declared later in + this file, so it wins on order) pins the buttons right inside that + full-width box. This rule comes after `.idx-actions`, so it can restore the + left edge. Above 820px the control hugs its content, so the declaration is + a no-op there and the horizontal row is untouched. + */ + .idx-actions { + justify-content: flex-start; + } +} .extension-prompt-dialog { max-height: calc(100dvh - 48px); overflow-y: auto; diff --git a/apps/desktop/src/styles/tokens.css b/apps/desktop/src/styles/tokens.css index bc74a61093..baf0e84266 100644 --- a/apps/desktop/src/styles/tokens.css +++ b/apps/desktop/src/styles/tokens.css @@ -216,6 +216,22 @@ --motion-ease-standard: cubic-bezier(0.2, 0, 0, 1); /* Global type scale (D343). 1 = product ramp; Appearance multiplies --text-*. */ --font-scale: 1; + + /* + Spacing ladder. Theme-invariant, so it lives in the base `:root` block + alongside the other semantic tokens, and is also declared in the `@theme` + block below so Tailwind can see the scale. The duplication is deliberate: + Tailwind only emits a `@theme` variable once something consumes it (as a + utility or through `var()`), so a `var(--space-*)` reference in a + stylesheet could otherwise resolve to nothing and the spacing would + silently collapse to zero. + */ + --space-1: 4px; + --space-2: 8px; + --space-3: 12px; + --space-4: 16px; + --space-5: 20px; + --space-6: 24px; } :root[data-theme="light"] { @@ -328,7 +344,10 @@ Token scales — the ONLY allowed values for typography and radii. Raw px/em literals for font-size, font-weight, line-height, letter-spacing, and border-radius are forbidden in component CSS - and TSX (enforced by scripts/check-style-tokens.mjs). + and TSX (enforced by scripts/check-style-tokens.mjs). The `--space-*` + ladder below is the same kind of scale for margin/padding/gap; it is not + yet machine-enforced because the renderer still carries a large backlog of + raw px spacing that would fail a repo-wide check. */ /* Apple-inspired fixed-radius ladder for dense desktop UI. Compact controls @@ -392,4 +411,18 @@ --tracking-tight: -0.02em; --tracking-normal: 0em; --tracking-wide: 0.02em; + + /* + Spacing ladder (design-system §6.1). Four-pixel rhythm; `--space-5` fills + the gap the doc leaves between 16 and 24 so the ramp stays linear. Values + match the documented scale exactly (space-1 4, space-2 8, space-3 12, + space-4 16, space-6 24). Mirrored in the base `:root` block above for the + reason noted there; keep the two copies in lockstep. + */ + --space-1: 4px; + --space-2: 8px; + --space-3: 12px; + --space-4: 16px; + --space-5: 20px; + --space-6: 24px; } diff --git a/apps/desktop/test/index-settings.test.mjs b/apps/desktop/test/index-settings.test.mjs new file mode 100644 index 0000000000..e1a571b026 --- /dev/null +++ b/apps/desktop/test/index-settings.test.mjs @@ -0,0 +1,134 @@ +import assert from "node:assert/strict"; +import { access, readFile } from "node:fs/promises"; +import { constants } from "node:fs"; +import test from "node:test"; + +const read = (path) => readFile(new URL(`../${path}`, import.meta.url), "utf8"); + +const [search, settingsPage, api, protocol, main, page, enLocale, settingsTypes, statsTypes] = await Promise.all([ + read("src/lib/settings-search.ts"), + read("src/features/settings/SettingsPage.tsx"), + read("src/lib/api.ts"), + read("../../packages/shared/src/protocol.ts"), + read("electron/main/ipc/session-ipc.ts"), + read("src/components/settings/IndexPage.tsx"), + read("../../packages/i18n/src/locales/en/index.ts"), + read("../../packages/shared/src/types/settings.ts"), + read("../../packages/shared/src/types/workspace-index.ts"), +]); + +test("workspace index is a workspace-group settings destination", () => { + assert.match(search, /id: "index"/); + assert.match(search, /labelKey: "settings\.nav\.index"/); + assert.match(search, /workspace: "settings\.groupWorkspace"/); + assert.match(search, /titleKey: "settings\.index"/); + // The index sits in Workspace, not in a group of its own (D335 / ADR 0173): + // the switch, its status and the rebuild/clear actions are host/workspace + // lifecycle, which is what that group already collects. The retired Data & + // Statistics group must not reappear as a one-entry section. + assert.doesNotMatch(search, /settings\.groupData/); + assert.doesNotMatch(search, /group: "data"/); + const entry = search.slice( + search.indexOf('id: "index"'), + search.indexOf('id: "about"'), + ); + assert.match(entry, /group: "workspace"/); + assert.match(entry, /"index\.card\.health"/); + assert.match(settingsPage, /tab === "index" && settings && \(\n\s*\n\s*\)/); + assert.match(settingsPage, /import \{ IndexPage \}/); +}); + +test("index page drives one switch and keeps the index a rebuildable cache", async () => { + await access( + new URL("../src/components/settings/IndexPage.tsx", import.meta.url), + constants.F_OK, + ); + assert.match(page, /api\.indexStatus\(/); + assert.match(page, /api\.indexRebuild\(/); + assert.match(page, /api\.indexClear\(/); + assert.match(page, /index\.card\.health/); + assert.match(page, /settings\.indexGrepBoost === true/); + assert.match(page, /saveSettings\(\{ indexGrepBoost: !grepBoost \}\)/); + assert.match(page, /setInterval\(poll, 1000\)/); + assert.match(page, /index\.status\.\$\{root\.status\}/); + // One switch owns the index lifecycle. A second "index new folders" toggle + // could only duplicate this one or build an index that nothing uses. + assert.equal(page.match(/role="switch"/g)?.length, 1); + assert.doesNotMatch(page, /indexNewFolders/); + assert.doesNotMatch(settingsTypes, /indexNewFolders/); + assert.doesNotMatch(enLocale, /newFolders/); + assert.match(enLocale, /grepBoost: "Workspace indexing"/); + assert.match(enLocale, /grepBoostDesc: "While this switch is on, newly opened workspaces are indexed/); + // The copy describes the index as what it is: a rebuildable local cache. + assert.match(enLocale, /statusDesc: "The index is a rebuildable local cache\."/); + // The manual build is gated on that same switch: without a consumer an index + // is only a scan and some disk, so the page must not offer to build one. + assert.match(page, /disabled=\{busy !== null \|\| !grepBoost\}/); + assert.match(page, /aria-describedby="idx-actions-desc"/); + assert.match(enLocale, /actionsDesc: "Build or rebuild the index for the current workspace/); + assert.match(enLocale, /cannot be built while the switch is off/); +}); + +test("index IPC stays on the three lifecycle channels", () => { + assert.match(protocol, /indexStatus: "pi-desktop\/index\/status"/); + assert.match(protocol, /indexRebuild: "pi-desktop\/index\/rebuild"/); + assert.match(protocol, /indexClear: "pi-desktop\/index\/clear"/); + assert.match(api, /indexStatus: \(rootPath\?: string\)/); + assert.match(api, /indexRebuild: \(rootPath\?: string\)/); + assert.match(api, /indexClear: \(rootPath\?: string\)/); + assert.match(main, /host\.call\("index\.status", input \?\? \{\}\)/); + assert.match(main, /host\.call\("index\.rebuild", input \?\? \{\}\)/); + assert.match(main, /host\.call\("index\.clear", input \?\? \{\}\)/); +}); + +test("settings search and locales carry the index keys", () => { + for (const key of [ + "settings.nav.index", + "settings.index", + "index.card.health", + "index.action.rebuild", + "index.action.clear", + ]) { + const leaf = key.split(".").pop(); + assert.match(enLocale, new RegExp(`${leaf}:`)); + } +}); + +test("index page surfaces the host's rebuild progress", () => { + assert.match(page, /className="idx-progress"/); + assert.match(page, /className="idx-progress-fill"/); + assert.match(page, /t\("index\.progressFiles", \{/); + assert.match(page, /t\("index\.progressFallback"\)/); + // The host gives no counts: an indeterminate pill, never a fake bar. + assert.match(page, /t\("index\.status\.building"\)/); +}); + +test("the index page explains itself once and can be dismissed for good", () => { + // Once written, the flag keeps the note away for every later render. + assert.match(page, /const NUDGE_DISMISSED_KEY = /); + assert.match(page, /localStorage\.getItem\(NUDGE_DISMISSED_KEY\) !== "1"/); + assert.match(page, /localStorage\.setItem\(NUDGE_DISMISSED_KEY, "1"\)/); + assert.match(page, /t\("index\.nudgeText"\)/); + assert.match(page, /t\("index\.nudgeDismiss"\)/); + // The switch left the health card: it is a setting, not telemetry. + assert.match(page, /t\("index\.sectionCode"\)/); + assert.match(page, /t\("index\.indexSubtitle"\)/); +}); + +test("the manual build is gated on the index switch", async () => { + // Rebuild stays a host lifecycle RPC and keeps working regardless, but the + // page only offers it while the opt-in switch is on: an index nothing uses + // is pure scan and disk cost, which is the reason the card carries exactly + // one toggle in the first place. + assert.match(page, /t\("index\.actionsDesc"\)/); + assert.match(page, /t\("index\.localOnly"\)/); + assert.match(page, /id="idx-actions-desc"/); + // Clear keeps working with the switch off, so a leftover index can still go. + assert.match(page, /disabled=\{busy !== null \|\| !root\}/); + // The gate lives in the locale, not only in the component: every shipped + // catalog has to say why the build can be unavailable. + for (const locale of ["en", "zh-CN", "zh-TW", "de", "es", "fr", "ko", "tr"]) { + const catalog = await read(`../../packages/i18n/src/locales/${locale}/index.ts`); + assert.match(catalog, /"?actionsDesc"?:/, locale); + } +}); diff --git a/apps/desktop/test/settings-general.test.mjs b/apps/desktop/test/settings-general.test.mjs index 85916be189..fed6850c18 100644 --- a/apps/desktop/test/settings-general.test.mjs +++ b/apps/desktop/test/settings-general.test.mjs @@ -400,6 +400,7 @@ test("settings nav icons map each destination to a semantic lucide glyph", () => assert.match(settingsPageSource, /agent: settingsSearchSource.indexOf(`id: "${id}"`)); assert.ok(navOrder.every((index) => index >= 0)); diff --git a/crates/host-core/src/index.rs b/crates/host-core/src/index.rs new file mode 100644 index 0000000000..50b72d2af9 --- /dev/null +++ b/crates/host-core/src/index.rs @@ -0,0 +1,962 @@ +//! Host-owned workspace content index storage. +//! +//! This module deliberately exposes only lifecycle operations (status, +//! rebuild, clear). Grep execution stays untouched: whether and how the index +//! may ever accelerate a search is a separate, independently reviewed change. + +use anyhow::{Context, Result}; +use rusqlite::{params, Connection, OptionalExtension}; +use serde::Serialize; +use sha2::{Digest, Sha256}; +use std::collections::HashMap; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, Mutex}; +use std::time::{SystemTime, UNIX_EPOCH}; + +pub mod fts; + +pub const MAX_FILES: usize = 50_000; +pub const MAX_FILE_BYTES: u64 = 1024 * 1024; +pub const MAX_INDEXED_BYTES: u64 = 2 * 1024 * 1024 * 1024; +// v2 stores the whole *visible* file set, not just the ingested subset: the +// `files` table gained `content_indexed`. The index is a rebuildable cache, so +// `open` quarantines a v1 database and re-crawls rather than migrating. +const INDEX_SCHEMA_VERSION: i64 = 2; + +#[allow(dead_code)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum IndexStatus { + Fresh, + Building, + Stale, + Failed, + Partial, + Disabled, + SkippedOverLimit, +} + +impl IndexStatus { + fn as_str(self) -> &'static str { + match self { + Self::Fresh => "fresh", + Self::Building => "building", + Self::Stale => "stale", + Self::Failed => "failed", + Self::Partial => "partial", + Self::Disabled => "disabled", + Self::SkippedOverLimit => "skipped_over_limit", + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct IndexLimits { + pub max_files: usize, + pub max_file_bytes: u64, + pub max_indexed_bytes: u64, +} + +impl Default for IndexLimits { + fn default() -> Self { + Self { + max_files: MAX_FILES, + max_file_bytes: MAX_FILE_BYTES, + max_indexed_bytes: MAX_INDEXED_BYTES, + } + } +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RootStatus { + pub root_id: String, + pub root_path: String, + pub status: String, + pub file_count: i64, + pub indexed_bytes: i64, + pub error_count: i64, + pub last_error: Option, + pub updated_at: i64, + /// Only present while `status == "building"`; omitted otherwise so the + /// non-building response shape is unchanged. + #[serde(skip_serializing_if = "Option::is_none")] + pub progress: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RootProgress { + pub files_done: u64, + pub files_total: u64, +} + +#[derive(Debug, Clone)] +struct IndexedFile { + rel_path: String, + size: u64, + mtime_ms: i64, + /// `None` when the file is visible but was never ingested (binary + /// extension, over-size text, unreadable). Such files are still stored so + /// the persisted set stays equal to the set Grep can reach. + body: Option, +} + +#[derive(Debug, Clone, Default)] +struct ScanResult { + file_count: i64, + ingested_count: i64, + indexed_bytes: u64, + error_count: i64, + over_limit: bool, +} + +/// Result of [`IndexStore::ensure_index`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum EnsureOutcome { + /// The root already had a fresh index; nothing to do. + Fresh, + /// A background rebuild is already in flight. + InProgress, + /// The root was marked `building`; the caller must run `rebuild` in the + /// background to finish it. + Triggered, +} + +#[derive(Debug)] +struct RootUpdate<'a> { + status: IndexStatus, + file_count: i64, + indexed_bytes: i64, + error_count: i64, + last_error: Option<&'a str>, +} + +#[derive(Debug, Clone)] +pub struct IndexStore { + path: PathBuf, + /// Roots with a build running in *this* process, each with its own live + /// crawl counters. A `building` row in the store without a matching entry + /// here is crash residue from a previous host process, and may be + /// re-armed instead of answered InProgress; an entry whose root already + /// has one means a second build must wait, not interleave. + building_roots: Arc>>>, + /// Set when opening the real store failed: every operation then answers + /// as unavailable instead of blocking host startup. Indexing is an + /// optimization layer, so losing it must not cost the boot. + disabled: bool, +} + +/// Live crawl counters for a `building` root. `total` starts as an estimate +/// (the previous visible file count) and `done` advances as files are seen. +#[derive(Debug, Default)] +pub struct BuildProgress { + done: std::sync::atomic::AtomicU64, + total: std::sync::atomic::AtomicU64, +} + +impl BuildProgress { + pub fn reset(&self, total_estimate: u64) { + use std::sync::atomic::Ordering; + self.done.store(0, Ordering::Relaxed); + self.total.store(total_estimate, Ordering::Relaxed); + } + + pub fn inc_done(&self) { + use std::sync::atomic::Ordering; + self.done.fetch_add(1, Ordering::Relaxed); + } + + /// `(filesDone, filesTotal)`. The total never reports less than the count + /// already processed, so a stale estimate cannot show >100%. + pub fn snapshot(&self) -> (u64, u64) { + use std::sync::atomic::Ordering; + let done = self.done.load(Ordering::Relaxed); + let total = self.total.load(Ordering::Relaxed).max(done); + (done, total) + } +} + +/// Removes a root from [`IndexStore::building_roots`] when the build call +/// ends, whatever the outcome. +struct BuildingGuard<'a>(&'a Mutex>>, String); + +impl Drop for BuildingGuard<'_> { + fn drop(&mut self) { + if let Ok(mut roots) = self.0.lock() { + roots.remove(&self.1); + } + } +} + +impl IndexStore { + pub fn open(data_dir: &Path) -> Result { + let directory = data_dir.join("index"); + std::fs::create_dir_all(&directory).context("create index directory")?; + let path = directory.join("index.db"); + let store = Self { + path, + building_roots: Arc::new(Mutex::new(HashMap::new())), + disabled: false, + }; + if let Err(error) = store.initialize() { + store.quarantine_corrupt_db(); + store + .initialize() + .with_context(|| format!("rebuild index database after failure: {error}"))?; + } + Ok(store) + } + + /// A store that answers every operation as unavailable. Used when opening + /// the real store failed and the quarantine-and-retry could not recover + /// it: every RPC reports the store as unavailable and the host keeps + /// booting. + pub fn disabled() -> Self { + Self { + path: PathBuf::new(), + building_roots: Arc::new(Mutex::new(HashMap::new())), + disabled: true, + } + } + + pub fn status(&self, root: Option<&Path>) -> Result> { + let connection = self.connection()?; + let normalized = root.map(normalize_root); + let root_filter = normalized + .as_ref() + .map(|path| path.to_string_lossy().into_owned()); + let mut statement = connection.prepare( + "SELECT root_id, root_path, status, file_count, indexed_bytes, error_count, last_error, updated_at + FROM indexed_roots + WHERE (?1 IS NULL OR root_path = ?1) + ORDER BY root_path", + )?; + let rows = statement.query_map([root_filter], |row| { + Ok(RootStatus { + root_id: row.get(0)?, + root_path: row.get(1)?, + status: row.get(2)?, + file_count: row.get(3)?, + indexed_bytes: row.get(4)?, + error_count: row.get(5)?, + last_error: row.get(6)?, + updated_at: row.get(7)?, + // Filled in below: the row callback cannot borrow `self`. + progress: None, + }) + })?; + let mut statuses = rows.collect::>>()?; + let building = self.building_roots.lock().unwrap().clone(); + for status in &mut statuses { + if status.status == IndexStatus::Building.as_str() { + if let Some(progress) = building.get(&status.root_id) { + let (files_done, files_total) = progress.snapshot(); + status.progress = Some(RootProgress { + files_done, + files_total, + }); + } + } + } + Ok(statuses) + } + + pub fn rebuild(&self, root: &Path, limits: IndexLimits) -> Result { + let root = normalize_root(root); + if !root.is_dir() { + anyhow::bail!("workspace root does not exist: {}", root.display()); + } + let root_id = root_id(&root); + // Register the build (with its own progress counters) before the row + // flips, and hold the registration for the whole call — the guard + // also covers early returns — so a `building` row that outlives the + // process is recognizable as crash residue, not a running build. + // A root already registered by an earlier call in this process keeps + // that registration and its progress. A genuinely concurrent second + // rebuild sees the root already registered *and its guard held* only + // via ensure_index's InProgress answer; direct double-entry adopts + // the existing counters rather than interleaving a second crawl's + // totals into the card. + let progress = { + let mut roots = self.building_roots.lock().unwrap(); + match roots.get(&root_id) { + Some(existing) => existing.clone(), + None => { + let progress = Arc::new(BuildProgress::default()); + roots.insert(root_id.clone(), progress.clone()); + progress + } + } + }; + let _guard = BuildingGuard(&self.building_roots, root_id.clone()); + // Seed the progress denominator from the previous visible set (or 0 on + // a first build). The crawler advances `done` as it visits files. + let previous_files: i64 = self + .connection()? + .query_row( + "SELECT COUNT(*) FROM files WHERE root_id = ?1", + [&root_id], + |row| row.get(0), + ) + .unwrap_or(0); + progress.reset(previous_files.max(0) as u64); + self.set_root_status( + &root_id, + &root, + RootUpdate { + status: IndexStatus::Building, + file_count: 0, + indexed_bytes: 0, + error_count: 0, + last_error: None, + }, + )?; + + let connection = self.connection()?; + // Files stream from the crawler into one open transaction — bodies + // never accumulate in memory, so a 2GB budget costs SQLite page + // cache, not heap. + let mut writer = RootWriter::begin(&connection, &root_id)?; + let scan = scan_root(&root, limits, Some(&progress), |file| { + writer.write(&root_id, &file) + }); + match scan { + Ok(result) => { + writer.commit()?; + let status = if result.over_limit { + IndexStatus::SkippedOverLimit + } else if result.error_count > 0 { + IndexStatus::Partial + } else { + IndexStatus::Fresh + }; + let message = if result.over_limit { + Some("index budget exceeded; fast-path use is disabled".to_string()) + } else if result.error_count > 0 { + Some(format!( + "{} file(s) could not be indexed", + result.error_count + )) + } else { + None + }; + upsert_root( + &connection, + &root_id, + &root, + RootUpdate { + status, + // The health card's "files indexed" figure stays about + // ingested content. The visible-but-unindexed rows + // exist for search completeness, not for display. + file_count: result.ingested_count, + indexed_bytes: result.indexed_bytes as i64, + error_count: result.error_count, + last_error: message.as_deref(), + }, + )?; + } + Err(error) => { + upsert_root( + &connection, + &root_id, + &root, + RootUpdate { + status: IndexStatus::Failed, + file_count: 0, + indexed_bytes: 0, + error_count: 1, + last_error: Some(&error.to_string()), + }, + )?; + } + } + self.status(Some(&root))? + .into_iter() + .next() + .context("index status missing after rebuild") + } + + /// Mark an unindexed/stale workspace as `building` without scanning, so a + /// caller can run [`IndexStore::rebuild`] off the hot path. Idempotent: + /// a fresh root stays fresh and a building root is not re-marked. + pub fn ensure_index(&self, root: &Path) -> Result { + let root = normalize_root(root); + if !root.is_dir() { + anyhow::bail!("workspace root does not exist: {}", root.display()); + } + let root_id = root_id(&root); + let building_in_process = self.building_roots.lock().unwrap().contains_key(&root_id); + match self.status(Some(&root))?.into_iter().next() { + Some(status) if status.status == IndexStatus::Fresh.as_str() => { + Ok(EnsureOutcome::Fresh) + } + // A live build answers InProgress. A `building` row with no + // in-process build behind it is crash residue — the previous host + // process died mid-build — so it falls through and re-arms + // instead of answering InProgress forever. + Some(status) + if status.status == IndexStatus::Building.as_str() && building_in_process => + { + Ok(EnsureOutcome::InProgress) + } + _ => { + // Register before the row flips, so the health card sees a + // progress block as soon as the root reports `building`. The + // total is seeded from the previous visible set — the same + // figure the spawned rebuild will use — so the card never + // shows 0/0. The spawned rebuild adopts this registration + // instead of creating a second one; a concurrent caller sees + // the in-process entry and answers InProgress. + let previous_files: i64 = self + .connection()? + .query_row( + "SELECT COUNT(*) FROM files WHERE root_id = ?1", + [&root_id], + |row| row.get(0), + ) + .unwrap_or(0); + let progress = Arc::new(BuildProgress::default()); + progress.reset(previous_files.max(0) as u64); + self.building_roots + .lock() + .unwrap() + .entry(root_id.clone()) + .or_insert(progress); + self.set_root_status( + &root_id, + &root, + RootUpdate { + status: IndexStatus::Building, + file_count: 0, + indexed_bytes: 0, + error_count: 0, + last_error: None, + }, + )?; + Ok(EnsureOutcome::Triggered) + } + } + } + + pub fn clear(&self, root: Option<&Path>) -> Result { + let mut connection = self.connection()?; + let transaction = connection.transaction()?; + let count = if let Some(root) = root { + let normalized = normalize_root(root); + let root_id = transaction + .query_row( + "SELECT root_id FROM indexed_roots WHERE root_path = ?1", + [normalized.to_string_lossy().into_owned()], + |row| row.get::<_, String>(0), + ) + .optional()?; + let Some(root_id) = root_id else { + return Ok(0); + }; + transaction.execute( + "DELETE FROM file_content_fts WHERE root_id = ?1", + [&root_id], + )?; + transaction.execute("DELETE FROM indexed_roots WHERE root_id = ?1", [&root_id])? + } else { + transaction.execute("DELETE FROM file_content_fts", [])?; + transaction.execute("DELETE FROM indexed_roots", [])? + }; + transaction.commit()?; + Ok(count) + } + + /// Test-only: the relative paths currently stored for `root`. With schema + /// v2 this is the whole *visible* set (ingested or not), which is exactly + /// what the Grep-vs-index diff test needs to compare. + #[cfg(test)] + pub fn indexed_rel_paths(&self, root: &Path) -> Result> { + let normalized = normalize_root(root).to_string_lossy().into_owned(); + let connection = self.connection()?; + let mut statement = connection.prepare( + "SELECT f.rel_path FROM files AS f + JOIN indexed_roots AS r ON r.root_id = f.root_id + WHERE r.root_path = ?1 + ORDER BY f.rel_path", + )?; + let rows = statement.query_map([normalized], |row| row.get::<_, String>(0))?; + rows.collect::>>() + .map_err(Into::into) + } + + fn connection(&self) -> Result { + if self.disabled { + anyhow::bail!("index store is unavailable"); + } + fts::open(&self.path) + } + + fn initialize(&self) -> Result<()> { + let connection = self.connection()?; + let version: i64 = connection.query_row("PRAGMA user_version", [], |row| row.get(0))?; + if version != 0 && version != INDEX_SCHEMA_VERSION { + anyhow::bail!("unsupported index schema version {version}"); + } + if version == 0 { + connection.execute_batch(fts::SCHEMA)?; + connection.pragma_update(None, "user_version", INDEX_SCHEMA_VERSION)?; + } + let integrity: String = + connection.query_row("PRAGMA integrity_check", [], |row| row.get(0))?; + if integrity != "ok" { + anyhow::bail!("index database integrity check failed: {integrity}"); + } + Ok(()) + } + + fn quarantine_corrupt_db(&self) { + if !self.path.exists() { + return; + } + let stamp = now_ms(); + let quarantined = self.path.with_extension(format!("db.corrupt-{stamp}")); + let _ = std::fs::rename(&self.path, quarantined); + let _ = std::fs::remove_file(self.path.with_extension("db-wal")); + let _ = std::fs::remove_file(self.path.with_extension("db-shm")); + } + + fn set_root_status(&self, root_id: &str, root: &Path, update: RootUpdate<'_>) -> Result<()> { + let connection = self.connection()?; + upsert_root(&connection, root_id, root, update) + } +} + +fn scan_root( + root: &Path, + limits: IndexLimits, + progress: Option<&BuildProgress>, + mut sink: impl FnMut(IndexedFile) -> Result<()>, +) -> Result { + let mut result = ScanResult::default(); + // The crawler and Grep share one visible-set definition; see + // `crate::tools::ignore_rules`. The crawler always covers the whole root, + // so it uses the unscoped walk. + for entry in crate::tools::ignore_rules::visible_walker(root, false).build() { + let entry = match entry { + Ok(entry) => entry, + Err(_) => { + result.error_count += 1; + continue; + } + }; + if !entry + .file_type() + .is_some_and(|file_type| file_type.is_file()) + || crate::tools::ignore_rules::is_vendor_path(root, entry.path()) + { + continue; + } + if result.file_count >= limits.max_files as i64 { + result.over_limit = true; + break; + } + // One visible file counts as progress, whether or not it is ingested. + if let Some(progress) = progress { + progress.inc_done(); + } + let metadata = match entry.metadata() { + Ok(metadata) => metadata, + Err(_) => { + result.error_count += 1; + continue; + } + }; + let size = metadata.len(); + // The content filters below decide whether a *visible* file is worth + // ingesting — not whether it exists. A filtered file still gets a row + // (with `content_indexed = 0`) so the stored set keeps matching the set + // Grep can reach; the fast path then re-scans it instead of losing it. + let body = if size <= limits.max_file_bytes && !fts::is_binary_extension(entry.path()) { + match std::fs::read_to_string(entry.path()) { + Ok(body) => Some(body), + Err(_) => { + result.error_count += 1; + None + } + } + } else { + None + }; + if body.is_some() { + if result.indexed_bytes.saturating_add(size) > limits.max_indexed_bytes { + result.over_limit = true; + break; + } + result.indexed_bytes = result.indexed_bytes.saturating_add(size); + result.ingested_count += 1; + } + let rel_path = entry + .path() + .strip_prefix(root) + .map(normalize_rel_path) + .unwrap_or_else(|_| normalize_rel_path(entry.path())); + result.file_count += 1; + sink(IndexedFile { + rel_path, + size, + mtime_ms: metadata + .modified() + .ok() + .and_then(|time| time.duration_since(UNIX_EPOCH).ok()) + .map(|duration| duration.as_millis() as i64) + .unwrap_or(0), + body, + })?; + } + Ok(result) +} + +/// A per-file writer into an open rebuild transaction. Files stream straight +/// from the crawler into SQLite — nothing accumulates the bodies in memory. +/// A per-file writer over one open rebuild transaction. Files stream +/// straight from the crawler into SQLite — nothing accumulates the bodies in +/// memory. The two INSERT statements are prepared from the connection before +/// the transaction opens (SQLite transactions are connection-scoped, so +/// stepping them inside is equivalent and keeps the writer free of +/// self-referential borrows). +struct RootWriter<'conn> { + insert_file: rusqlite::Statement<'conn>, + insert_fts: rusqlite::Statement<'conn>, + transaction: rusqlite::Transaction<'conn>, +} + +impl<'conn> RootWriter<'conn> { + fn begin(connection: &'conn Connection, root_id: &str) -> Result { + let insert_file = connection.prepare( + "INSERT INTO files (root_id, rel_path, size, mtime_ms, content_indexed) VALUES (?1, ?2, ?3, ?4, ?5)", + )?; + let insert_fts = connection.prepare( + "INSERT INTO file_content_fts (root_id, rel_path, body) VALUES (?1, ?2, ?3)", + )?; + let transaction = connection.unchecked_transaction()?; + // Old rows go first: the FTS hit set must never outlive the files + // rows it points into. + transaction.execute("DELETE FROM file_content_fts WHERE root_id = ?1", [root_id])?; + transaction.execute("DELETE FROM files WHERE root_id = ?1", [root_id])?; + Ok(Self { + insert_file, + insert_fts, + transaction, + }) + } + + fn write(&mut self, root_id: &str, file: &IndexedFile) -> Result<()> { + self.insert_file.execute(params![ + root_id, + file.rel_path, + file.size as i64, + file.mtime_ms, + if file.body.is_some() { 1_i64 } else { 0_i64 } + ])?; + // Only ingested files reach the full-text table, so an FTS hit always + // implies a readable body behind it. + if let Some(body) = &file.body { + self.insert_fts + .execute(params![root_id, file.rel_path, body])?; + } + Ok(()) + } + + fn commit(self) -> Result<()> { + self.transaction.commit()?; + Ok(()) + } +} + +fn upsert_root( + connection: &Connection, + root_id: &str, + root: &Path, + update: RootUpdate<'_>, +) -> Result<()> { + connection.execute( + "INSERT INTO indexed_roots (root_id, root_path, status, file_count, indexed_bytes, error_count, last_error, updated_at) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8) + ON CONFLICT(root_id) DO UPDATE SET root_path=excluded.root_path, status=excluded.status, + file_count=excluded.file_count, indexed_bytes=excluded.indexed_bytes, error_count=excluded.error_count, + last_error=excluded.last_error, updated_at=excluded.updated_at", + params![ + root_id, + normalize_root(root).to_string_lossy().into_owned(), + update.status.as_str(), + update.file_count, + update.indexed_bytes, + update.error_count, + update.last_error, + now_ms() + ], + )?; + Ok(()) +} + +pub fn normalize_root(path: &Path) -> PathBuf { + let mut text = path.to_string_lossy().replace('\\', "/"); + if let Some(rest) = text.strip_prefix("//?/") { + text = rest.to_string(); + } + let candidate = PathBuf::from(text); + candidate.canonicalize().unwrap_or(candidate) +} + +pub fn normalize_rel_path(path: &Path) -> String { + path.to_string_lossy().replace('\\', "/") +} + +pub fn root_id(root: &Path) -> String { + let mut hash = Sha256::new(); + hash.update(normalize_root(root).to_string_lossy().as_bytes()); + hex::encode(hash.finalize()) +} + +fn now_ms() -> i64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|duration| duration.as_millis() as i64) + .unwrap_or_default() +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + + #[test] + fn normalizes_root_and_relative_paths() { + let directory = tempfile::tempdir().unwrap(); + let root = normalize_root(&PathBuf::from(format!("{}/", directory.path().display()))); + assert_eq!(root, directory.path().canonicalize().unwrap()); + assert_eq!(normalize_rel_path(Path::new("src\\lib.rs")), "src/lib.rs"); + assert_eq!(root_id(&root), root_id(&root)); + } + + #[test] + fn empty_store_is_safe() { + let data = tempfile::tempdir().unwrap(); + let store = IndexStore::open(data.path()).unwrap(); + assert!(store.status(None).unwrap().is_empty()); + assert_eq!(store.clear(None).unwrap(), 0); + } + + #[test] + fn building_residue_from_a_dead_process_is_rearmed() { + let data = tempfile::tempdir().unwrap(); + let root = tempfile::tempdir().unwrap(); + fs::write(root.path().join("one.txt"), "one\n").unwrap(); + let store = IndexStore::open(data.path()).unwrap(); + // Simulate crash residue: a `building` row with no in-process build + // behind it (as if the previous host process died mid-build). + store + .set_root_status( + &root_id(&normalize_root(root.path())), + &normalize_root(root.path()), + RootUpdate { + status: IndexStatus::Building, + file_count: 0, + indexed_bytes: 0, + error_count: 0, + last_error: None, + }, + ) + .unwrap(); + assert!(matches!( + store.ensure_index(root.path()).unwrap(), + EnsureOutcome::Triggered, + )); + + // The same row while a build IS running in this process must still + // answer InProgress. + let rebuilding = IndexStore::open(data.path()).unwrap(); + rebuilding + .set_root_status( + &root_id(&normalize_root(root.path())), + &normalize_root(root.path()), + RootUpdate { + status: IndexStatus::Building, + file_count: 0, + indexed_bytes: 0, + error_count: 0, + last_error: None, + }, + ) + .unwrap(); + rebuilding.building_roots.lock().unwrap().insert( + root_id(&normalize_root(root.path())), + Arc::new(BuildProgress::default()), + ); + assert!(matches!( + rebuilding.ensure_index(root.path()).unwrap(), + EnsureOutcome::InProgress, + )); + } + + #[test] + fn disabled_store_answers_every_read_as_unavailable() { + let store = IndexStore::disabled(); + assert!(store.status(None).is_err()); + assert!(store.ensure_index(Path::new("/nonexistent")).is_err()); + } + + #[test] + fn rebuild_indexes_text_and_ignores_binary_and_vendor_dirs() { + let data = tempfile::tempdir().unwrap(); + let root = tempfile::tempdir().unwrap(); + fs::create_dir_all(root.path().join("node_modules/pkg")).unwrap(); + fs::create_dir_all(root.path().join(".git")).unwrap(); + fs::write(root.path().join(".pi-desktopignore"), "private.txt\n").unwrap(); + fs::write(root.path().join("README.md"), "hello index\n").unwrap(); + fs::write(root.path().join("notes.md"), "indexed too\n").unwrap(); + fs::write(root.path().join("private.txt"), "private\n").unwrap(); + fs::write(root.path().join("node_modules/pkg/ignored.js"), "ignored\n").unwrap(); + fs::write(root.path().join("image.png"), [0_u8, 1, 2, 3]).unwrap(); + let store = IndexStore::open(data.path()).unwrap(); + let status = store.rebuild(root.path(), IndexLimits::default()).unwrap(); + assert_eq!(status.status, "fresh"); + assert_eq!(status.file_count, 2); + assert!(status.indexed_bytes > 0); + // The binary file is visible but not ingested. Both halves matter: it + // must be recorded (so the fast path still searches it) yet must not + // count as indexed content. + assert_eq!( + store.indexed_rel_paths(root.path()).unwrap(), + vec![ + "README.md".to_string(), + "image.png".to_string(), + "notes.md".to_string() + ] + ); + } + + #[test] + fn budget_marks_root_skipped_without_serving_partial_index() { + let data = tempfile::tempdir().unwrap(); + let root = tempfile::tempdir().unwrap(); + fs::write(root.path().join("one.txt"), "one\n").unwrap(); + fs::write(root.path().join("two.txt"), "two\n").unwrap(); + let store = IndexStore::open(data.path()).unwrap(); + let status = store + .rebuild( + root.path(), + IndexLimits { + max_files: 1, + ..IndexLimits::default() + }, + ) + .unwrap(); + assert_eq!(status.status, "skipped_over_limit"); + } + + #[test] + fn corrupt_store_is_quarantined_and_recreated() { + let data = tempfile::tempdir().unwrap(); + let path = data.path().join("index/index.db"); + fs::create_dir_all(path.parent().unwrap()).unwrap(); + fs::write(&path, b"not sqlite").unwrap(); + let store = IndexStore::open(data.path()).unwrap(); + assert!(store.status(None).unwrap().is_empty()); + assert!(fs::read_dir(path.parent().unwrap()).unwrap().count() >= 2); + } + + #[test] + fn multiple_roots_are_namespaced_and_clear_is_scoped() { + let data = tempfile::tempdir().unwrap(); + let root_a = tempfile::tempdir().unwrap(); + let root_b = tempfile::tempdir().unwrap(); + fs::write(root_a.path().join("a.txt"), "alpha\n").unwrap(); + fs::write(root_b.path().join("b.txt"), "bravo\n").unwrap(); + let store = IndexStore::open(data.path()).unwrap(); + store + .rebuild(root_a.path(), IndexLimits::default()) + .unwrap(); + store + .rebuild(root_b.path(), IndexLimits::default()) + .unwrap(); + assert_eq!(store.status(None).unwrap().len(), 2); + assert_eq!(store.clear(Some(root_a.path())).unwrap(), 1); + let remaining = store.status(None).unwrap(); + assert_eq!(remaining.len(), 1); + assert_eq!( + remaining[0].root_path, + normalize_root(root_b.path()).to_string_lossy() + ); + + // The FTS side of the cleared root must go too: the virtual table has + // no foreign keys, so clear deletes its rows explicitly. A leftover + // row would silently keep workspace content readable after "clear". + let connection = fts::open(&store.path).unwrap(); + let orphaned: i64 = connection + .query_row("SELECT COUNT(*) FROM file_content_fts", [], |row| { + row.get(0) + }) + .unwrap(); + assert_eq!(orphaned, 1); + let files_left: i64 = connection + .query_row("SELECT COUNT(*) FROM files", [], |row| row.get(0)) + .unwrap(); + assert_eq!(files_left, 1); + } + + #[test] + fn status_reports_progress_only_while_building() { + let data = tempfile::tempdir().unwrap(); + let root = tempfile::tempdir().unwrap(); + fs::write(root.path().join("a.txt"), "alpha\n").unwrap(); + let store = IndexStore::open(data.path()).unwrap(); + + // An unindexed root marked building (as the auto-index path does) + // surfaces the progress block; a never-built root seeds the total at 0. + assert_eq!( + store.ensure_index(root.path()).unwrap(), + crate::index::EnsureOutcome::Triggered + ); + let building = store + .status(Some(root.path())) + .unwrap() + .into_iter() + .next() + .unwrap(); + assert_eq!(building.status, "building"); + let progress = building.progress.expect("building exposes progress"); + assert_eq!(progress.files_done, 0); + + // A fresh root carries no progress field. + let fresh = store.rebuild(root.path(), IndexLimits::default()).unwrap(); + assert_eq!(fresh.status, "fresh"); + assert!(fresh.progress.is_none()); + + // Marking the freshly built root stale makes the next ensure_index mark + // it building again; the progress denominator is seeded from the + // previous visible set (1 file). + fts::open(&store.path) + .unwrap() + .execute("UPDATE indexed_roots SET status = 'stale'", []) + .unwrap(); + assert_eq!( + store.ensure_index(root.path()).unwrap(), + crate::index::EnsureOutcome::Triggered + ); + let rebuilding = store + .status(Some(root.path())) + .unwrap() + .into_iter() + .next() + .unwrap(); + let progress = rebuilding.progress.expect("building exposes progress"); + assert_eq!(progress.files_total, 1); + } + + #[test] + fn progress_never_reports_total_below_done() { + let progress = BuildProgress::default(); + progress.reset(1); + progress.inc_done(); + progress.inc_done(); + progress.inc_done(); + assert_eq!(progress.snapshot(), (3, 3)); + } +} diff --git a/crates/host-core/src/index/fts.rs b/crates/host-core/src/index/fts.rs new file mode 100644 index 0000000000..ce48810a32 --- /dev/null +++ b/crates/host-core/src/index/fts.rs @@ -0,0 +1,81 @@ +//! SQLite storage for the workspace content index: the schema, the +//! connection opener, and the pragmas every writer relies on. + +use anyhow::{Context, Result}; +use rusqlite::Connection; +use std::path::Path; + +/// Binary/archival extensions the index never ingests. This is a *content* +/// filter, not a visibility rule: such files stay visible to Grep and are +/// simply not worth indexing. Vendor/ignore visibility lives in +/// `crate::tools::ignore_rules`. +pub(crate) fn is_binary_extension(path: &Path) -> bool { + path.extension() + .and_then(|extension| extension.to_str()) + .is_some_and(|extension| { + matches!( + extension.to_ascii_lowercase().as_str(), + "7z" | "a" + | "bmp" + | "class" + | "dll" + | "dmg" + | "exe" + | "gif" + | "ico" + | "jar" + | "jpeg" + | "jpg" + | "mov" + | "mp3" + | "mp4" + | "o" + | "obj" + | "pdf" + | "png" + | "so" + | "tar" + | "wasm" + | "webp" + | "woff" + | "woff2" + | "zip" + ) + }) +} + +pub const SCHEMA: &str = r#" + PRAGMA journal_mode = WAL; + PRAGMA foreign_keys = ON; + CREATE TABLE IF NOT EXISTS indexed_roots ( + root_id TEXT PRIMARY KEY, + root_path TEXT NOT NULL UNIQUE, + status TEXT NOT NULL CHECK (status IN ('fresh','building','stale','failed','partial','disabled','skipped_over_limit')), + file_count INTEGER NOT NULL DEFAULT 0, + indexed_bytes INTEGER NOT NULL DEFAULT 0, + error_count INTEGER NOT NULL DEFAULT 0, + last_error TEXT, + updated_at INTEGER NOT NULL DEFAULT 0 + ); + CREATE TABLE IF NOT EXISTS files ( + root_id TEXT NOT NULL REFERENCES indexed_roots(root_id) ON DELETE CASCADE, + rel_path TEXT NOT NULL, + size INTEGER NOT NULL, + mtime_ms INTEGER NOT NULL, + content_indexed INTEGER NOT NULL DEFAULT 1, + PRIMARY KEY (root_id, rel_path) + ); + CREATE VIRTUAL TABLE IF NOT EXISTS file_content_fts USING fts5( + root_id UNINDEXED, + rel_path UNINDEXED, + body, + tokenize = 'trigram' + ); +"#; + +pub fn open(path: &Path) -> Result { + let connection = Connection::open(path).context("open index database")?; + connection.busy_timeout(std::time::Duration::from_secs(5))?; + connection.execute_batch("PRAGMA foreign_keys = ON; PRAGMA journal_mode = WAL;")?; + Ok(connection) +} diff --git a/crates/host-core/src/main.rs b/crates/host-core/src/main.rs index 3b50366781..7c9032f424 100644 --- a/crates/host-core/src/main.rs +++ b/crates/host-core/src/main.rs @@ -6,6 +6,7 @@ mod background_tasks; mod config_sync; mod data_relocation; mod db; +mod index; mod keyboard; mod mcp_servers; mod network_policy; diff --git a/crates/host-core/src/rpc/mod.rs b/crates/host-core/src/rpc/mod.rs index 4f86af66bd..520556e7b3 100644 --- a/crates/host-core/src/rpc/mod.rs +++ b/crates/host-core/src/rpc/mod.rs @@ -660,6 +660,24 @@ fn response_for( } } +fn checked_index_root( + requested: Option, + current: PathBuf, +) -> Result { + let current = crate::index::normalize_root(¤t); + let requested = requested + .map(|root| crate::index::normalize_root(&root)) + .unwrap_or_else(|| current.clone()); + if requested != current { + return Err(rpc_err( + 1002, + "rootPath must match the active workspace", + "INDEX_ROOT_OUTSIDE_WORKSPACE", + )); + } + Ok(current) +} + fn config_sync_rpc_err(error: impl ToString) -> JsonRpcError { let message = error.to_string(); let error_code = message @@ -980,6 +998,16 @@ fn effective_command_shell_id(settings: Option<&Value>) -> Option { .map(|shell| shell.id) } +/// Whether the workspace index switch is on. Absent or `false` leaves the +/// index alone: nothing builds one and the index RPCs still answer status, +/// so the switch is the single owner of when a workspace gets indexed. +fn index_grep_boost_enabled(settings: Option<&Value>) -> bool { + settings + .and_then(|value| value.get("indexGrepBoost")) + .and_then(Value::as_bool) + .unwrap_or(false) +} + fn validate_settings_value(value: &Value) -> Result<(), JsonRpcError> { let Some(object) = value.as_object() else { return Ok(()); @@ -1151,6 +1179,17 @@ fn validate_settings_value(value: &Value) -> Result<(), JsonRpcError> { if let Err(message) = crate::network_proxy::validate_network_proxy(value) { return Err(rpc_err(1002, message, "INVALID_PARAMS")); } + // The index switch is opt-in. Reject non-booleans so a malformed patch + // cannot switch it on through JSON truthiness. + if let Some(flag) = object.get("indexGrepBoost") { + if !flag.is_boolean() { + return Err(rpc_err( + 1002, + "indexGrepBoost must be a boolean", + "INVALID_PARAMS", + )); + } + } let Some(shell_value) = object.get("defaultCommandShell") else { return Ok(()); }; @@ -1938,6 +1977,129 @@ async fn handle_request( .map_err(|e| rpc_err(1000, e.to_string(), "INTERNAL"))?; Ok(json!({ "projects": projects })) } + "index.status" => { + let started = std::time::Instant::now(); + let requested_root = params + .get("rootPath") + .and_then(Value::as_str) + .map(PathBuf::from); + let (index, current_root) = { + let st = state.lock().await; + ( + st.index.clone(), + st.workspace + .get() + .map(|workspace| PathBuf::from(workspace.path)), + ) + }; + let Some(current_root) = current_root else { + tracing::info!( + method = "index.status", + duration_ms = started.elapsed().as_millis() as u64, + "index rpc served" + ); + return Ok(json!({ "roots": [] })); + }; + let root = checked_index_root(requested_root, current_root)?; + let roots = tokio::task::spawn_blocking(move || index.status(Some(&root))) + .await + .map_err(|e| rpc_err(1000, e.to_string(), "INDEX_UNAVAILABLE"))? + .map_err(|e| rpc_err(1000, e.to_string(), "INDEX_UNAVAILABLE"))?; + tracing::info!( + method = "index.status", + roots = roots.len(), + duration_ms = started.elapsed().as_millis() as u64, + "index rpc served" + ); + Ok(json!({ "roots": roots })) + } + "index.rebuild" => { + let started = std::time::Instant::now(); + let requested_root = params + .get("rootPath") + .and_then(Value::as_str) + .map(PathBuf::from); + let (index, current_root) = { + let st = state.lock().await; + ( + st.index.clone(), + st.workspace + .get() + .map(|workspace| PathBuf::from(workspace.path)), + ) + }; + let current_root = current_root + .ok_or_else(|| rpc_err(1002, "active workspace required", "INVALID_PARAMS"))?; + let root = checked_index_root(requested_root, current_root)?; + let audit_root = root.to_string_lossy().into_owned(); + let status = tokio::task::spawn_blocking(move || { + index.rebuild(&root, crate::index::IndexLimits::default()) + }) + .await + .map_err(|e| rpc_err(1000, e.to_string(), "INDEX_REBUILD_FAILED"))? + .map_err(|e| rpc_err(1000, e.to_string(), "INDEX_REBUILD_FAILED"))?; + // Destructive lifecycle operation: record who rebuilt which root. + // Only the redacted summary fields go to the audit log — never any + // file content. + let st = state.lock().await; + let _ = audit::append( + &st.db, + "index_rebuild", + None, + json!({ + "rootPath": audit_root, + "status": status.status, + "fileCount": status.file_count, + "errorCount": status.error_count, + }), + ); + tracing::info!( + method = "index.rebuild", + status = %status.status, + file_count = status.file_count, + duration_ms = started.elapsed().as_millis() as u64, + "index rpc served" + ); + Ok(json!({ "root": status })) + } + "index.clear" => { + let started = std::time::Instant::now(); + let requested_root = params + .get("rootPath") + .and_then(Value::as_str) + .map(PathBuf::from); + let (index, current_root) = { + let st = state.lock().await; + ( + st.index.clone(), + st.workspace + .get() + .map(|workspace| PathBuf::from(workspace.path)), + ) + }; + let current_root = current_root + .ok_or_else(|| rpc_err(1002, "active workspace required", "INVALID_PARAMS"))?; + let root = checked_index_root(requested_root, current_root)?; + let audit_root = root.to_string_lossy().into_owned(); + let cleared = tokio::task::spawn_blocking(move || index.clear(Some(&root))) + .await + .map_err(|e| rpc_err(1000, e.to_string(), "INDEX_UNAVAILABLE"))? + .map_err(|e| rpc_err(1000, e.to_string(), "INDEX_UNAVAILABLE"))?; + let st = state.lock().await; + let _ = audit::append( + &st.db, + "index_clear", + None, + json!({ "rootPath": audit_root, "cleared": cleared }), + ); + tracing::info!( + method = "index.clear", + cleared, + duration_ms = started.elapsed().as_millis() as u64, + "index rpc served" + ); + Ok(json!({ "ok": true, "cleared": cleared })) + } "project.groups.list" => { let st = state.lock().await; let groups = st @@ -2222,6 +2384,7 @@ async fn handle_request( .and_then(|v| v.as_str()) .ok_or_else(|| rpc_err(1002, "path required", "INVALID_PARAMS"))?; let mut st = state.lock().await; + let previous = st.workspace.get().map(|workspace| workspace.path); st.hashline.drop_all(); let ws = if path.starts_with(crate::db::REMOTE_PROJECT_PREFIX) { let group = st @@ -2241,6 +2404,48 @@ async fn handle_request( st.db .kv_set("app", "currentProjectId", &json!(pid)) .map_err(|e| rpc_err(1000, e.to_string(), "INTERNAL"))?; + // Auto-index the workspace while the index switch is on: one + // switch owns the index's whole lifecycle. A changed path always + // (re)indexes; the scan runs on the blocking pool, so workspace.set + // stays fast and `index.status` reports `building` until it lands. + let settings = st.db.get_setting("app").ok().flatten(); + let changed = previous.as_deref() != Some(ws.path.as_str()); + let index = st.index.clone(); + let root = PathBuf::from(ws.path.clone()); + let boost = index_grep_boost_enabled(settings.as_ref()); + // The index store owns its connection, so its calls below do not + // need the app state lock; drop the lock before doing them so + // concurrent RPCs are not serialized behind this one. + drop(st); + if boost && changed { + match index.ensure_index(&root) { + Ok(crate::index::EnsureOutcome::Triggered) => { + let build_index = index.clone(); + let build_root = root.clone(); + let handle = tokio::task::spawn_blocking(move || { + if let Err(error) = build_index + .rebuild(&build_root, crate::index::IndexLimits::default()) + { + tracing::warn!(error = %error, "background index rebuild failed"); + } + }); + // Keep the join handle reachable so tests (and any + // future caller) can await the build instead of + // polling the store for a status flip. + let mut st = state.lock().await; + st.index_builds.insert( + crate::index::normalize_root(&root) + .to_string_lossy() + .into_owned(), + handle, + ); + } + Ok(_) => {} + Err(error) => { + tracing::warn!(error = %error, "auto index ensure failed"); + } + } + } Ok(json!({ "workspace": ws })) } "workspace.clear" => { @@ -2345,6 +2550,10 @@ async fn handle_request( { gate_default_command_shell_setting(&st)?; } + // Captured before `stored` is consumed by the merge below: the + // index-switch check compares the incoming switch against the + // stored one. + let boost_was_on = index_grep_boost_enabled(stored.as_ref()); let mut settings = normalize_settings_value(merge_settings_value(stored, params)); prune_unresolvable_image_bindings(&st.db, &mut settings) .map_err(|e| rpc_err(1000, e.to_string(), "INTERNAL"))?; @@ -2368,6 +2577,44 @@ async fn handle_request( } crate::network_proxy::apply_from_settings(Some(&settings)); crate::network_policy::apply_from_settings(Some(&settings)); + // Turning the index switch on must arm the index for the + // workspace the user is looking at; otherwise the index only + // exists after the next workspace switch, and the switch's + // lifetime is disjoint from the page that reports on it. + if index_grep_boost_enabled(Some(&settings)) && !boost_was_on { + if let Some(workspace) = st.workspace.get() { + let index = st.index.clone(); + let root = PathBuf::from(workspace.path); + drop(st); + match index.ensure_index(&root) { + Ok(crate::index::EnsureOutcome::Triggered) => { + let build_index = index.clone(); + let build_root = root.clone(); + let handle = tokio::task::spawn_blocking(move || { + if let Err(error) = build_index + .rebuild(&build_root, crate::index::IndexLimits::default()) + { + tracing::warn!( + error = %error, + "boost-enable index build failed" + ); + } + }); + let mut st = state.lock().await; + st.index_builds.insert( + crate::index::normalize_root(&root) + .to_string_lossy() + .into_owned(), + handle, + ); + } + Ok(_) => {} + Err(error) => { + tracing::warn!(error = %error, "boost-enable ensure failed"); + } + } + } + } Ok(json!({ "ok": true })) } @@ -5436,10 +5683,11 @@ mod tests { use tokio::sync::{mpsc, Mutex}; use super::{ - capability_err, handle_request, parse_capability_query, parse_capability_target, - peek_jsonrpc_id, provider_rpc_err, request_budget_ms, resolve_plan_workspace, - resolve_tool_workspace, resolve_tool_workspace_for_call, response_for, rpc_err, scope_err, - skill_err, with_request_budget, JsonRpcError, RPC_REQUEST_BUDGET_MS, + capability_err, handle_request, index_grep_boost_enabled, parse_capability_query, + parse_capability_target, peek_jsonrpc_id, provider_rpc_err, request_budget_ms, + resolve_plan_workspace, resolve_tool_workspace, resolve_tool_workspace_for_call, response_for, + rpc_err, scope_err, skill_err, validate_settings_value, with_request_budget, JsonRpcError, + RPC_REQUEST_BUDGET_MS, }; use crate::agent_capabilities::CapabilityLevel; use crate::plans::{PlanResolveParams, PlanSubmitParams}; @@ -8236,6 +8484,128 @@ mod tests { .unwrap(); } + #[test] + fn index_grep_boost_defaults_off_and_rejects_non_booleans() { + assert!(!index_grep_boost_enabled(None)); + assert!(!index_grep_boost_enabled(Some(&json!({})))); + assert!(!index_grep_boost_enabled(Some( + &json!({ "indexGrepBoost": false }) + ))); + assert!(index_grep_boost_enabled(Some( + &json!({ "indexGrepBoost": true }) + ))); + + assert!(validate_settings_value(&json!({ "indexGrepBoost": true })).is_ok()); + assert!(validate_settings_value(&json!({ "theme": "light" })).is_ok()); + // A truthy string must not be able to switch the index on. + assert!(validate_settings_value(&json!({ "indexGrepBoost": "true" })).is_err()); + assert!(validate_settings_value(&json!({ "indexGrepBoost": 1 })).is_err()); + } + + #[tokio::test] + async fn workspace_set_auto_indexes_only_while_the_switch_is_on() { + let data_dir = tempfile::tempdir().unwrap(); + let workspace = tempfile::tempdir().unwrap(); + std::fs::write(workspace.path().join("auto.txt"), "auto index target\n").unwrap(); + let mut app_state = AppState::open(data_dir.path()).unwrap(); + app_state.handshook = true; + let state = Arc::new(Mutex::new(app_state)); + let (tx, _rx) = mpsc::unbounded_channel(); + + // Switch off (the default): no index rows are created for the root. + handle_request( + state.clone(), + "workspace.set", + json!({ "path": workspace.path().display().to_string() }), + tx.clone(), + ) + .await + .unwrap(); + let off = handle_request(state.clone(), "index.status", json!({}), tx.clone()) + .await + .unwrap(); + assert_eq!(off["roots"].as_array().unwrap().len(), 0); + + // Turn the switch on, then switch to a different workspace: the + // background build must land at a fresh root. The build's join handle + // is awaited directly — no wall-clock polling — so the assertion reads + // the settled status the moment the build completes. + handle_request( + state.clone(), + "settings.set", + json!({ "indexGrepBoost": true }), + tx.clone(), + ) + .await + .unwrap(); + let other = tempfile::tempdir().unwrap(); + std::fs::write(other.path().join("other.txt"), "other workspace\n").unwrap(); + handle_request( + state.clone(), + "workspace.set", + json!({ "path": other.path().display().to_string() }), + tx.clone(), + ) + .await + .unwrap(); + let build = { + let mut st = state.lock().await; + st.index_builds.remove( + &crate::index::normalize_root(other.path()) + .to_string_lossy() + .into_owned(), + ) + }; + let build = build.expect("workspace.set registered the background build"); + build.await.expect("background build task panicked"); + let status = handle_request(state.clone(), "index.status", json!({}), tx.clone()) + .await + .unwrap(); + let roots = status["roots"].as_array().unwrap(); + let root = roots.first().expect("indexed root present"); + assert_eq!(root["status"], "fresh"); + assert_eq!(root["fileCount"], 1); + } + + #[tokio::test] + async fn index_rebuild_and_clear_are_audited() { + let data_dir = tempfile::tempdir().unwrap(); + let workspace = tempfile::tempdir().unwrap(); + std::fs::write(workspace.path().join("audit.txt"), "audit target\n").unwrap(); + let mut app_state = AppState::open(data_dir.path()).unwrap(); + app_state.handshook = true; + let state = Arc::new(Mutex::new(app_state)); + let (tx, _rx) = mpsc::unbounded_channel(); + + handle_request( + state.clone(), + "workspace.set", + json!({ "path": workspace.path().display().to_string() }), + tx.clone(), + ) + .await + .unwrap(); + handle_request(state.clone(), "index.rebuild", json!({}), tx.clone()) + .await + .unwrap(); + handle_request(state.clone(), "index.clear", json!({}), tx.clone()) + .await + .unwrap(); + + let st = state.lock().await; + let mut kinds: Vec = st + .db + .conn() + .prepare("SELECT kind FROM audit_log WHERE kind LIKE 'index_%' ORDER BY id") + .unwrap() + .query_map([], |row| row.get::<_, String>(0)) + .unwrap() + .collect::>>() + .unwrap(); + kinds.dedup(); + assert_eq!(kinds, vec!["index_rebuild", "index_clear"]); + } + #[tokio::test] async fn bash_rejects_a_changed_shell_before_running_the_command() { let Some(current_shell_id) = available_test_shell_id() else { diff --git a/crates/host-core/src/state.rs b/crates/host-core/src/state.rs index 56d4734917..ce1ef39df5 100644 --- a/crates/host-core/src/state.rs +++ b/crates/host-core/src/state.rs @@ -5,6 +5,7 @@ use std::time::{Duration, Instant}; use anyhow::Result; use crate::db::Database; +use crate::index::IndexStore; use crate::mcp_servers::McpServerRegistry; use crate::permissions::PermissionManager; use crate::plans::PlanManager; @@ -27,6 +28,14 @@ const PLUGIN_DELETE_RATE_LIMIT: usize = 20; pub struct AppState { pub data_dir: std::path::PathBuf, pub db: Database, + /// Workspace content index (FTS5 store under `/index/index.db`). + /// Lifecycle only — status/rebuild/clear; it does not participate in any + /// tool execution. + pub index: IndexStore, + /// normalized root → join handle of the background build that + /// `workspace.set`/`settings.set` spawned, so callers (and tests) can + /// await a build's completion instead of polling the wall clock. + pub index_builds: HashMap>, pub secrets: SecretStore, pub workspace: WorkspaceState, pub permissions: PermissionManager, @@ -106,6 +115,17 @@ impl AppState { Err(error) => tracing::warn!(%error, "in-flight reply sweep failed"), } let secrets = SecretStore::open(data_dir)?; + // The index is an optimization layer: if its store cannot be opened + // even after the quarantine-and-retry, degrade to a disabled store + // (the status RPC reports unavailable) instead of costing the host + // its boot. + let index = match IndexStore::open(data_dir) { + Ok(index) => index, + Err(error) => { + tracing::warn!(%error, "index store unavailable; indexing stays disabled"); + IndexStore::disabled() + } + }; // The marketplace channel is read before the manager builds its first // catalog, so a source configured for networks without GitHub access // applies on launch instead of only after a manual refresh. @@ -131,6 +151,8 @@ impl AppState { Ok(Self { data_dir: data_dir.to_path_buf(), db, + index, + index_builds: HashMap::new(), secrets, workspace: WorkspaceState::default(), permissions: PermissionManager::default(), diff --git a/crates/host-core/src/tools/ignore_rules.rs b/crates/host-core/src/tools/ignore_rules.rs index 09b549c7b0..3e57c613cd 100644 --- a/crates/host-core/src/tools/ignore_rules.rs +++ b/crates/host-core/src/tools/ignore_rules.rs @@ -209,6 +209,47 @@ pub fn rg_args(ignore_root: &Path, scoped: bool) -> Vec { args } +// ---- P2-B workspace visible-set additions -------------------------------- +// +// The index crawler reuses this module so the index visible set and the Grep +// candidate walk stay structurally identical: both are configured by +// [`configure_walker`], whose layers pair with [`rg_args`] on the rg side. + +/// Directory names that are tooling or scan metadata rather than workspace +/// content. The index crawler prunes these before ingest in addition to the +/// ignore layers above, so their bytes never reach the FTS store. +pub const VENDOR_COMPONENTS: &[&str] = &[".git", ".pi-desktopignore", "node_modules", "target"]; + +/// Build the shared visible-set walker for `root`. +/// +/// `scoped` mirrors Grep's scoped search: when the caller names a path +/// explicitly, parent ignore files are dropped so an explicitly named +/// directory stays reachable. The index crawler always walks the whole root +/// (`scoped == false`). +pub fn visible_walker(root: &Path, scoped: bool) -> WalkBuilder { + let mut walker = WalkBuilder::new(root); + walker.hidden(false).git_ignore(true); + if scoped { + // Same as rg's --no-ignore-parent for a scoped search: an explicitly + // named directory stays reachable even when a parent directory + // ignores it. + walker.parents(false); + } + configure_walker(&mut walker, root, scoped); + walker +} + +/// Whether a walked path sits under a [`VENDOR_COMPONENTS`] entry. Only +/// applied to a whole-workspace (unscoped) search. +pub fn is_vendor_path(root: &Path, path: &Path) -> bool { + let relative = path.strip_prefix(root).unwrap_or(path); + relative.components().any(|component| { + VENDOR_COMPONENTS + .iter() + .any(|vendor| component.as_os_str() == *vendor) + }) +} + #[cfg(test)] mod tests { use super::*; @@ -260,3 +301,84 @@ mod tests { assert!(unscoped.contains(&"!*.log".to_string())); } } + +#[cfg(test)] +mod vendor_visibility_tests { + use super::*; + use std::fs; + + fn visible_from_walker(root: &Path, scoped: bool) -> Vec { + let mut paths: Vec = visible_walker(root, scoped) + .build() + .flatten() + .filter(|entry| entry.file_type().is_some_and(|kind| kind.is_file())) + .map(|entry| entry.path().to_path_buf()) + .filter(|path| scoped || !is_vendor_path(root, path)) + .map(|path| { + path.strip_prefix(root) + .unwrap_or(&path) + .to_string_lossy() + .replace('\\', "/") + }) + .collect(); + paths.sort(); + paths + } + + #[test] + fn vendor_and_custom_ignore_drop_from_whole_workspace_visibility() { + let root = tempfile::tempdir().unwrap(); + fs::create_dir_all(root.path().join("node_modules/pkg")).unwrap(); + fs::create_dir_all(root.path().join(".git")).unwrap(); + fs::write(root.path().join(".pi-desktopignore"), "private.txt\n").unwrap(); + fs::write(root.path().join("keep.txt"), "keep\n").unwrap(); + fs::write(root.path().join("private.txt"), "secret\n").unwrap(); + fs::write(root.path().join("node_modules/pkg/a.js"), "x\n").unwrap(); + fs::write(root.path().join(".git/config"), "[core]\n").unwrap(); + + assert_eq!(visible_from_walker(root.path(), false), vec!["keep.txt"]); + } + + #[test] + fn explicitly_named_vendor_directory_stays_reachable() { + let root = tempfile::tempdir().unwrap(); + fs::create_dir_all(root.path().join("node_modules/pkg")).unwrap(); + fs::write(root.path().join("node_modules/pkg/index.js"), "x\n").unwrap(); + + // Scoped: no vendor prune, so the named directory is visible. + assert_eq!( + visible_from_walker(&root.path().join("node_modules/pkg"), true), + vec!["index.js"] + ); + } + + #[test] + fn scoped_walk_drops_parent_ignore_files_like_the_rg_side() { + let root = tempfile::tempdir().unwrap(); + fs::write(root.path().join(".ignore"), "node_modules\n").unwrap(); + fs::create_dir_all(root.path().join("node_modules/pkg")).unwrap(); + fs::write(root.path().join("node_modules/pkg/index.js"), "x\n").unwrap(); + + // A scoped walk rooted below the ignore file must not honor it — + // the same contract rg gets via --no-ignore-parent — while the + // whole-workspace walk keeps filtering (only the ignore file itself + // stays visible; `node_modules/` is dropped). + assert_eq!( + visible_from_walker(&root.path().join("node_modules/pkg"), true), + vec!["index.js"] + ); + assert_eq!(visible_from_walker(root.path(), false), vec![".ignore"]); + } + + #[test] + fn root_pi_desktopignore_constrains_the_whole_scan() { + let root = tempfile::tempdir().unwrap(); + fs::create_dir_all(root.path().join("sub")).unwrap(); + fs::write(root.path().join(".pi-desktopignore"), "*.log\n").unwrap(); + fs::write(root.path().join("app.log"), "x\n").unwrap(); + fs::write(root.path().join("sub/deep.log"), "x\n").unwrap(); + fs::write(root.path().join("main.rs"), "x\n").unwrap(); + + assert_eq!(visible_from_walker(root.path(), false), vec!["main.rs"]); + } +} diff --git a/docs/spec/03-runtime/01-ipc-protocol.md b/docs/spec/03-runtime/01-ipc-protocol.md index 64c0fa9051..6ec902a423 100644 --- a/docs/spec/03-runtime/01-ipc-protocol.md +++ b/docs/spec/03-runtime/01-ipc-protocol.md @@ -36,6 +36,7 @@ Principles: | `notification` | Durable inbox list/read/clear, new/activated events, and native-notification sound cues | | `stats` | Completed-turn token history (host RPC; dashboard is plugin-owned) | | `voice/live` | App-owned real-time calls, secret-free status/settings DTOs, and per-call media ports | +| `index` | Workspace index lifecycle (host RPC; builds and reports a local cache behind the opt-in `indexGrepBoost` switch) | ## 3. Channel Conventions @@ -1232,6 +1233,20 @@ ISO week year (`%G-W%V`). The result fills empty buckets in range. This channel is not a Settings page; the user-facing dashboard is plugin `pi.token-insights` (D335 / ADR 0173). +### index + +- `pi-desktop/index/status({ rootPath? }) -> { roots: WorkspaceIndexRoot[] }` +- `pi-desktop/index/rebuild({ rootPath? }) -> { root: WorkspaceIndexRoot }` +- `pi-desktop/index/clear({ rootPath? }) -> { ok, cleared }` + +Lifecycle channels for the host-owned workspace index cache. `rootPath` is +optional and must equal the active workspace when present +(`INDEX_ROOT_OUTSIDE_WORKSPACE` otherwise). `rebuild` scans the workspace into +`/index/index.db` under fixed file-count and byte budgets and returns +the resulting root status. `status` returns lifecycle metadata only and never +file contents. The cache is only built while the `indexGrepBoost` switch is on; +no tool reads it. + ## 8. Settings / Secrets API ### settings diff --git a/docs/spec/03-runtime/04-data-storage.md b/docs/spec/03-runtime/04-data-storage.md index 6d3f413bd1..7dddcd9b43 100644 --- a/docs/spec/03-runtime/04-data-storage.md +++ b/docs/spec/03-runtime/04-data-storage.md @@ -185,6 +185,9 @@ to an absolute path before it reaches host-core as a child-process variable. ├── plugins/ # code + data + registry.json (unchanged, spec 07-11) ├── logs/ # NDJSON app/, host/, agent/ logs ├── cache/ # disposable caches + ├── index/ # rebuildable workspace-search cache (host-core only) + │ ├── index.db # root metadata + file metadata + FTS5 text + │ └── index.db.corrupt- # quarantined cache after integrity/schema failure ├── crash-dumps/ # local Crashpad minidumps (never uploaded; D602) ├── crash-dumps.json # last-reported dump mtime (best-effort marker) ├── review-changes/// @@ -205,6 +208,16 @@ content lives in `sessions/`, attachments and tool outputs beyond the limits of [16-tool-result-limits](16-tool-result-limits.md) live on disk, referenced by path/hash. +`index/index.db` is deliberately separate from `pi.sqlite`. It is a disposable, +rebuildable optimization cache and never filesystem truth. It stores normalized +root and relative paths, file size and modification time, lifecycle/error +metadata, and indexed text in FTS5. It stores no credentials, message history, +project entity records, or file hashes. It is excluded from application backup, +export, and sync surfaces; corruption or an unsupported index schema quarantines +the old cache and creates a new empty index without touching `pi.sqlite`. +The index RPCs are lifecycle-only (`status`/`rebuild`/`clear`); no tool reads +this cache. + ### 1.3 Portable configuration sync Host Core stores S3 configuration at `configSyncS3/config` in the key-value diff --git a/docs/spec/03-runtime/06-host-rpc-protocol.md b/docs/spec/03-runtime/06-host-rpc-protocol.md index e03cfbd781..7ded5790a1 100644 --- a/docs/spec/03-runtime/06-host-rpc-protocol.md +++ b/docs/spec/03-runtime/06-host-rpc-protocol.md @@ -210,6 +210,24 @@ type ToolBudgetHealth = { - `workspace.set` - `workspace.clear` +### Workspace index +- `index.status({rootPath?})` returns lifecycle status for the host-owned, + disposable workspace index. It never exposes file contents. +- `index.rebuild({rootPath?})` scans the selected workspace into the isolated + `/index/index.db` cache. When `rootPath` is omitted, the current + workspace is used. The operation enforces fixed file-count and byte budgets. +- `workspace.set` triggers a background `ensure_index` + rebuild for a changed +workspace while `indexGrepBoost` is on; with it off, switching workspaces +never touches the index. `index.clear({rootPath?})` removes the active + workspace root namespace. When `rootPath` is present it must equal the active + workspace; omission selects that same workspace. + +The index database is a rebuildable optimization cache, not filesystem truth. +No tool reads it: `tools.execute` keeps its exact behavior whether or not an +index exists, and the index RPCs are the cache's only consumer. Roots report +`fresh`, `building`, `stale`, `failed`, `partial`, `disabled`, or +`skipped_over_limit`. + ### Review snapshots (ADR 0043) - `review.rollback({sessionId, snapshotId})` — verify the current post-tool hash, restore the session-owned previous bytes, and return one of diff --git a/docs/spec/03-runtime/08-error-codes.md b/docs/spec/03-runtime/08-error-codes.md index a73dde68a5..be751bc127 100644 --- a/docs/spec/03-runtime/08-error-codes.md +++ b/docs/spec/03-runtime/08-error-codes.md @@ -121,6 +121,9 @@ does not turn temporary thread pressure into a host process exit. |---|---|---| | `WORKSPACE_REQUIRED` | no | no workspace bound | | `PATH_OUTSIDE_WORKSPACE` | no | path escapes sandbox before an explicit outside-path permission decision, or a prompt attachment is outside its session scratch/project/attachment roots | +| `INDEX_UNAVAILABLE` | no | the index store could not be opened or the rebuild worker failed | +| `INDEX_ROOT_OUTSIDE_WORKSPACE` | no | index.status/rebuild/clear was called with a root that is not the active workspace root | +| `INDEX_REBUILD_FAILED` | no | a background index rebuild failed partway; the root is marked failed and can be rebuilt again | | `WORKSPACE_PATH_DENIED` | no | an explicit `Read`/`Write`/`Edit` path hit the always-on security denylist (private keys, `.env` files, credential bundles, `.git/objects`); an outside-path grant does not lift it (spec 15 §3) | | `READ_PATH_IS_DIRECTORY` | no | `Read` was given a directory; the result carries a `Glob` suggestion | | `TOOL_BINARY_CONTENT` | no | `Read` refused to dump a binary file into the model context | diff --git a/docs/spec/04-ux/06-settings-ia.md b/docs/spec/04-ux/06-settings-ia.md index abf6daaae7..1d5a6c04ad 100644 --- a/docs/spec/04-ux/06-settings-ia.md +++ b/docs/spec/04-ux/06-settings-ia.md @@ -57,14 +57,17 @@ Settings is a **full-window page** that replaces the app sidebar + main chrome ( 10. **Cloud sync / 云同步** — Lucide `CloudDownload` (encrypted portable configuration backup and bidirectional sync; Experimental, all builds) 11. **Remote Hosts / 远程主机** — Lucide `Globe` (experimental SSH hosts, sessions, and explicit model copy) 12. **Info / 信息** — Lucide `Info` (versions, logs, updates, developer) + 9. **Import / 导入** — Lucide `Download` (bring sessions and model configuration in from other tools) + 11. **Index / 索引** — Lucide `Database` (workspace index health and lifecycle) Icons are decorative (`aria-hidden` via the SVG default) and stay monochrome with the rail label; do not reuse refresh/rotate glyphs here. - The directory remains a flat searchable list in the same exact order. For scanability, the destinations are shown in four titled visual clusters: `Preferences` / `偏好` (General, AI, Shortcuts), `Agent` / `智能体` (Instructions, Models, Skills, MCP, Subagents), `Workspace` / `工作区` - (Projects), and `System` / `系统` (Cloud sync, Remote Hosts, Info; Cloud sync - and Remote Hosts are Experimental destinations available in all builds). Headings are + (Projects, Index), and `System` / `系统` (Cloud sync, Remote Hosts, Info; + Cloud sync and Remote Hosts are Experimental destinations available in all + builds). Headings are muted, non-interactive labels and use whitespace for separation; no divider lines are rendered. These are visual landmarks only, not a second navigation level. @@ -862,6 +865,29 @@ system while preserving their different data ownership: the project becomes visible in the home sidebar; merely selecting its row for inspection does not restore it. +### Index library (`index` tab, `Workspace` group) +- One health card for the host-owned workspace index cache: status, indexed + file count, indexed size, unreadable-file count, and last-update time +- A standalone `Codebase` section below the health card carries the single + opt-in toggle, `Workspace indexing` (`indexGrepBoost`, default off). When + on, opening a different workspace marks its index `building` and rebuilds + it on the blocking pool, so `workspace.set` stays fast and the health card + polls `index.status` once a second while building, pausing when the window + is hidden. No tool reads the index: it is reported on, rebuilt, and cleared + from this page alone. A second "index new folders" toggle would either + duplicate this switch or build an index that nothing uses, so the section + carries exactly one +- Actions are Rebuild index and Clear index; both call the host lifecycle + RPCs and refresh the card from the returned status. Rebuild is offered only + while the opt-in toggle is on, because an index the switch never feeds is + just a scan and some disk; Clear stays available so a leftover index can + still be removed +- The copy states that the index is a rebuildable local cache whose data + never leaves the machine +- Empty state: no root yet for the active workspace, with Build index as the + single action, gated the same way. Load failure shows a retry instead of a + blank card + ### Info - app/host/protocol versions + open logs - **Report a problem** row: one action opens the GitHub bug issue form in @@ -922,6 +948,11 @@ system while preserving their different data ownership: mode on. The rows are grouped under Preferences / 偏好, Agent / 智能体, Workspace / 工作区, and System / 系统. There is no Usage / 用量 destination. + Subagents / 子智能体, Import / 导入, Projects / 项目, Index / 索引, Cloud sync / + 云同步, and Info / 信息 in that order. Cloud sync / 云同步 + and Remote Hosts / 远程主机 appear only while developer mode is on. The rows are grouped + under Preferences / 偏好, Agent / 智能体, Workspace / 工作区, and + System / 系统. There is no Usage / 用量 destination. 3. Appearance is part of General and has no standalone rail destination 4. Providers is part of Agent and has no standalone rail destination 5. Plugins has no Settings destination; the app-shell Plugins page supports diff --git a/docs/spec/06-delivery/04-e2e-test-plan.md b/docs/spec/06-delivery/04-e2e-test-plan.md index f5db95ea1f..d39b4b0cb4 100644 --- a/docs/spec/06-delivery/04-e2e-test-plan.md +++ b/docs/spec/06-delivery/04-e2e-test-plan.md @@ -2756,6 +2756,66 @@ identify the platform validation still needed. - **Milestone**: M2 - **Status**: Draft +### Workspace Index + +#### E2E-INDEX-status-rebuild-clear: isolated index lifecycle stays workspace-scoped + +- **Preconditions**: A debug host-core binary is available; the harness can create + temporary data, workspace, and outside directories. +- **Steps**: 1) Start host-core with an isolated `PI_DESKTOP_DATA_DIR`. 2) Set the + temporary workspace. 3) Confirm `index.status` is empty. 4) Call + `index.rebuild` and confirm one fixture file is indexed. 5) Attempt to rebuild + an outside directory. 6) Clear the workspace root and read status again. +- **Expected**: Rebuild produces one `fresh` root; status exposes metadata but no + file content; the outside root fails with `INDEX_ROOT_OUTSIDE_WORKSPACE`; + clear removes exactly the selected root and leaves status empty. All index + files live under the temporary data directory and are removed by harness + cleanup. Grep results stay identical whether or not the cache exists. +- **Specs linked**: `03-runtime/04-data-storage.md`, + `03-runtime/06-host-rpc-protocol.md` +- **Acceptance**: C (workspace tools and boundaries), Quality (data safety) +- **Milestone**: M6+ +- **Status**: Automated by `pnpm test:e2e:index` + +#### E2E-INDEX-settings-health-card: index health card reflects host lifecycle + +- **Preconditions**: The app is running with an active workspace. The Index + destination is available under the Workspace settings group. +- **Steps**: 1) Open Settings → Index. 2) Observe the empty state for a + workspace without an index. 3) Activate Build index and wait for the card to + refresh. 4) Activate Clear index. 5) Optionally point the window at a + workspace whose index is over budget or partial. +- **Expected**: The card shows the host-reported status, indexed file count, + indexed size, unreadable-file count when non-zero, and last-update time. + Build and Clear call the host lifecycle RPCs and refresh from the returned + status. The copy describes the index as a rebuildable local cache and never + claims Grep reads it. Load failure shows a retry action instead of a blank + card. Grep results stay unchanged throughout. +- **Specs linked**: `04-ux/06-settings-ia.md`, + `03-runtime/06-host-rpc-protocol.md`, `03-runtime/04-data-storage.md` +- **Acceptance**: D (workspace), Quality (local data safety) +- **Milestone**: M6+ +- **Status**: Documented; the RPC lifecycle is covered by + `pnpm test:e2e:index`, UI automation is pending + +#### E2E-INDEX-auto-index: switching workspaces builds the index only when switched on + +- **Preconditions**: Host RPC available; two temporary workspaces; the switch + at its default. +- **Steps**: 1) `workspace.set` to workspace A with the switch off and read + `index.status`. 2) Turn `indexGrepBoost` on. 3) `workspace.set` to + workspace B and poll `index.status`. +- **Expected**: With the switch off, status stays empty. With it on, + the changed workspace is marked `building` immediately and a background + rebuild lands it at `fresh` with the fixture file counted; `workspace.set` + returns promptly without waiting for the scan. +- **Specs linked**: `03-runtime/06-host-rpc-protocol.md`, + `04-ux/06-settings-ia.md` +- **Acceptance**: D (workspace), Quality (responsiveness) +- **Milestone**: M6+ +- **Status**: Automated at the RPC boundary in host-core + (`workspace_set_auto_indexes_only_while_the_switch_is_on`) + ### Workspace Open #### E2E-012: Open a project directory @@ -10124,7 +10184,7 @@ must keep splitting are covered by `markdown-blocks.test.mjs`. | B — Model config | E2E-005, E2E-PROVIDER-custom-endpoint-no-api-key, E2E-006, E2E-007, E2E-038, E2E-050, E2E-052, E2E-055, E2E-066, E2E-080, E2E-082, E2E-102c, E2E-102d, E2E-102e, E2E-151, E2E-154, E2E-163, E2E-166, E2E-172, E2E-174, E2E-197, E2E-005G, E2E-005J, E2E-199, E2E-201, E2E-202, E2E-203, E2E-205, E2E-206, E2E-209, E2E-PLUGIN-provider-catalog-add-service | | C — Conversation & stream | E2E-CHAT-long-history-stream-keeps-responsive, E2E-CHAT-running-status-survives-output-pauses, E2E-008, E2E-008d, E2E-008e, E2E-008a, E2E-009, E2E-010, E2E-011, E2E-011a, E2E-011b, E2E-011d, E2E-011e, E2E-011g, E2E-031, E2E-040, E2E-047, E2E-048, E2E-048A, E2E-049, E2E-052, E2E-053, E2E-054, E2E-055, E2E-059, E2E-059a, E2E-060c, E2E-060d, E2E-061, E2E-061a, E2E-062, E2E-064, E2E-065, E2E-068, E2E-071, E2E-073, E2E-074, E2E-075, E2E-081, E2E-083, E2E-084, E2E-086, E2E-087, E2E-088, E2E-088b, E2E-089, E2E-090, E2E-COMPOSER-narrow-controls, E2E-094, E2E-095, E2E-096, E2E-097, E2E-098, E2E-099, E2E-102, E2E-102a, E2E-102b, E2E-102c, E2E-102d, E2E-102g, E2E-106, E2E-109, E2E-111, E2E-114, E2E-116, E2E-117, E2E-118, E2E-119, E2E-120, E2E-121, E2E-218, E2E-259, E2E-219, E2E-AGENTS-001, E2E-142, E2E-144, E2E-145, E2E-146, E2E-146a, E2E-147, E2E-151, E2E-154, E2E-155, E2E-158, E2E-159, E2E-161, E2E-162, E2E-166, E2E-172, E2E-173, E2E-174, E2E-177, E2E-178, E2E-179, E2E-180, E2E-182, E2E-183, E2E-187, E2E-198, E2E-199, E2E-202, E2E-203, E2E-207, E2E-208, E2E-CHAT-content-width-handles, E2E-250, E2E-102i, E2E-PLUGIN-session-orchestrator-real-workers, E2E-SUBAGENT-settlement-updates-before-parent-poll, E2E-SUBAGENT-resume-a-settled-delegation | | C — Conversation & stream (composer drafts) | E2E-011c, E2E-011c-1 | -| D — Workspace | E2E-012, E2E-013, E2E-022B, E2E-024I, E2E-047, E2E-049, E2E-057, E2E-058, E2E-060, E2E-068, E2E-075, E2E-078, E2E-153, E2E-158, E2E-182, E2E-187, E2E-252 | +| D — Workspace | E2E-INDEX-status-rebuild-clear, E2E-INDEX-settings-health-card, E2E-012, E2E-013, E2E-022B, E2E-024I, E2E-047, E2E-049, E2E-057, E2E-058, E2E-060, E2E-068, E2E-075, E2E-078, E2E-153, E2E-158, E2E-182, E2E-187, E2E-252 | | C / D / Quality — Project session history resets after collapse | E2E-263 | | D — Workspace (project ordering) | E2E-253 | | E — Tools & permissions | E2E-008a, E2E-014, E2E-015, E2E-016, E2E-017, E2E-018, E2E-019, E2E-024I, E2E-024K, E2E-040, E2E-049, E2E-074, E2E-093, E2E-097, E2E-099, E2E-100, E2E-101, E2E-102, E2E-102d, E2E-102e, E2E-102g, E2E-103, E2E-105, E2E-106, E2E-107, E2E-111, E2E-112, E2E-113, E2E-114, E2E-115, E2E-116, E2E-119, E2E-121, E2E-122, E2E-142, E2E-145, E2E-147, E2E-155, E2E-158, E2E-166, E2E-181, E2E-PLUGIN-imported-pi-package-skills | diff --git a/docs/zh-CN/spec/03-runtime/01-ipc-protocol.md b/docs/zh-CN/spec/03-runtime/01-ipc-protocol.md index 79a7716a22..70fab9afc1 100644 --- a/docs/zh-CN/spec/03-runtime/01-ipc-protocol.md +++ b/docs/zh-CN/spec/03-runtime/01-ipc-protocol.md @@ -39,6 +39,7 @@ | `notification` | 持久收件箱 list/read/clear 和 new/activated 事件 | | `stats` | 已完成回合的 token 历史(host RPC;仪表板由插件拥有) | | `voice/live` | 应用管理的实时语音通话、无凭证状态/设置 DTO 与每通电话专用媒体端口 | +| `index` | 工作区索引生命周期(host RPC;在 `indexGrepBoost` 开关开启时构建并报告本地缓存) | ## 3. 通道约定 @@ -959,6 +960,18 @@ Electron拥有本地化并提供面向用户的分支名称;主机 (`%G-W%V`)。结果会填充范围内的空桶。此通道不是设置页面;面向用户的仪表板 是插件 `pi.token-insights`(D335 / ADR 0173)。 +### index + +- `pi-desktop/index/status({ rootPath? }) -> { roots: WorkspaceIndexRoot[] }` +- `pi-desktop/index/rebuild({ rootPath? }) -> { root: WorkspaceIndexRoot }` +- `pi-desktop/index/clear({ rootPath? }) -> { ok, cleared }` + +host 所有的工作区索引缓存生命周期通道。`rootPath` 可选;提供时必须等于当前工作区 +(否则返回 `INDEX_ROOT_OUTSIDE_WORKSPACE`)。`rebuild` 在固定的文件数与字节预算下 +把工作区扫描进 `/index/index.db` 并返回 root 状态。`status` 只返回生命周期 +元数据,绝不返回文件内容。该缓存仅在 `indexGrepBoost` 开关开启时才会构建; +任何工具都不读取它。 + ## 8. 设置/秘密 API ### settings diff --git a/docs/zh-CN/spec/03-runtime/04-data-storage.md b/docs/zh-CN/spec/03-runtime/04-data-storage.md index 5b8bc49f74..6fc854e535 100644 --- a/docs/zh-CN/spec/03-runtime/04-data-storage.md +++ b/docs/zh-CN/spec/03-runtime/04-data-storage.md @@ -59,6 +59,9 @@ host-core 之前被解析为绝对路径。 ├── plugins/ # code + data + registry.json (unchanged, spec 07-11) ├── logs/ # NDJSON app/, host/, agent/ logs ├── cache/ # disposable caches + ├── index/ # 可重建的工作区搜索缓存(仅 host-core) + │ ├── index.db # root 元数据、文件元数据与 FTS5 文本 + │ └── index.db.corrupt- # 完整性/架构失败后隔离的缓存 ├── crash-dumps/ # local Crashpad minidumps (never uploaded; D602) ├── crash-dumps.json # last-reported dump mtime (best-effort marker) ├── review-changes/// @@ -78,6 +81,13 @@ host-core 之前被解析为绝对路径。 [16-tool-result-limits](/zh-CN/spec/03-runtime/16-tool-result-limits) 存在于磁盘上,已引用 由 path/hash 提供。 +`index/index.db` 刻意与 `pi.sqlite` 分离。它是可丢弃、可重建的优化缓存, +永远不是文件系统事实来源。它只保存规范化 root/相对路径、文件大小与修改时间、 +生命周期/错误元数据,以及 FTS5 中的索引文本;不保存凭据、消息历史、项目实体记录或文件 hash。 +它被排除在应用备份、导出与同步之外;完整性检查失败或索引架构版本不受支持时, +旧缓存会被隔离并创建新的空索引,且不会触碰 `pi.sqlite`。索引 RPC 只涉及生命周期 +(`status`/`rebuild`/`clear`);任何工具都不读取该缓存。 + ### 2.0 消息拥有的评论快照 (ADR 0043) 成功的工作区 `PRAGMA user_version`/Plan/Goal 工具结果携带有界 diff --git a/docs/zh-CN/spec/03-runtime/06-host-rpc-protocol.md b/docs/zh-CN/spec/03-runtime/06-host-rpc-protocol.md index b471398468..dc2ba6c9e8 100644 --- a/docs/zh-CN/spec/03-runtime/06-host-rpc-protocol.md +++ b/docs/zh-CN/spec/03-runtime/06-host-rpc-protocol.md @@ -186,6 +186,19 @@ type ToolBudgetHealth = { - `workspace.set` - `workspace.clear` +### 工作区索引 +- `index.status({rootPath?})` 返回 host 所有、可丢弃的工作区索引生命周期状态,不暴露文件内容。 +- `index.rebuild({rootPath?})` 将所选工作区扫描进独立的 + `/index/index.db` 缓存。省略 `rootPath` 时使用当前工作区;操作受固定文件数和字节预算限制。 +- `indexGrepBoost` 开启时,`workspace.set` 会对变更的工作区触发后台 +`ensure_index` + 重建;关闭时,切换工作区绝不触碰索引。 +`index.clear({rootPath?})` 删除当前工作区的 root namespace;提供 `rootPath` 时必须与当前工作区一致,省略时也选择当前工作区。 + +索引数据库是可重建的优化缓存,不是文件系统事实来源。任何工具都不读取它: +无论索引是否存在,`tools.execute` 的行为完全一致,索引 RPC 是该缓存唯一的消费方。 +root 状态包括 `fresh`、`building`、`stale`、`failed`、`partial`、 +`disabled`、`skipped_over_limit`。 + ### 查看快照 (ADR 0043) - `review.rollback({sessionId, snapshotId})` — 验证当前的后期工具 hash,恢复会话拥有的先前字节,并返回其中之一 diff --git a/docs/zh-CN/spec/03-runtime/08-error-codes.md b/docs/zh-CN/spec/03-runtime/08-error-codes.md index 49365f0183..9a674d058d 100644 --- a/docs/zh-CN/spec/03-runtime/08-error-codes.md +++ b/docs/zh-CN/spec/03-runtime/08-error-codes.md @@ -123,6 +123,9 @@ stdio 与 Tokio 的动态阻塞池隔离,因此后一种情况 |---|---|---| | `WORKSPACE_REQUIRED` | 不 | 无工作空间限制 | | `PATH_OUTSIDE_WORKSPACE` | 不 | 在显式外部路径权限决策之前路径逃逸沙箱,或提示词附件位于其会话 scratch/project/attachment 根目录之外 | +| `INDEX_UNAVAILABLE` | 不 | 索引存储无法打开,或重建工作线程失败 | +| `INDEX_ROOT_OUTSIDE_WORKSPACE` | 不 | index.status/rebuild/clear 传入了与当前活动工作区根不一致的 root | +| `INDEX_REBUILD_FAILED` | 不 | 后台索引重建中途失败;该 root 标记为 failed,可再次重建 | | `WORKSPACE_PATH_DENIED` | 不 | 显式的 `Read`/`Write`/`Edit` 路径命中了始终开启的安全拒绝名单(私钥、`.env` 文件、凭证包、`.git/objects`);外部路径授权不会解除它(规格 15 §3) | | `READ_PATH_IS_DIRECTORY` | 不 | `Read` 拿到的是目录;结果附带一条 `Glob` 建议 | | `TOOL_BINARY_CONTENT` | 不 | `Read` 拒绝把二进制文件倾倒进模型上下文 | diff --git a/docs/zh-CN/spec/04-ux/06-settings-ia.md b/docs/zh-CN/spec/04-ux/06-settings-ia.md index 39a02e805f..3e19ff5406 100644 --- a/docs/zh-CN/spec/04-ux/06-settings-ia.md +++ b/docs/zh-CN/spec/04-ux/06-settings-ia.md @@ -37,12 +37,15 @@ 10. **云同步** — Lucide `CloudDownload`(加密的可移植配置备份与双向同步;实验性,所有构建可见) 11. **远程主机** — Lucide `Globe`(SSH 引导与配对清单;实验性,所有构建可见) 12. **信息** — Lucide `Info`(版本、日志、更新、开发人员) + 9. **导入** — Lucide `Download`(从其他工具引入会话和模型配置) + 10. **项目** — Lucide `Archive`(持久项目索引) + 11. **索引** — Lucide `Database`(索引库:本地索引健康与生命周期) 图标具有装饰性(通过 SVG 默认设置为 `aria-hidden`)并保持单色 带有导轨标签;不要在此处重复使用 refresh/rotate 字形。 - 目录仍是保持相同顺序的可搜索扁平列表。为便于扫描,目的地分为四个带标题 的视觉分组:“偏好”(常规、AI、快捷键)、“智能体”(指令、模型、技能、 - MCP、子智能体)、“工作区”(项目)和“系统”(云同步、远程主机、信息; - 云同步和远程主机均为所有构建可见的实验性目的地)。标题使用柔和 + MCP、子智能体)、“工作区”(项目、索引)和“系统”(云同步、远程主机、 + 信息;云同步和远程主机均为所有构建可见的实验性目的地)。标题使用柔和 的非交互文字,分组之间只使用留白,不绘制分割线;搜索过滤后,空分组及其 标题一并隐藏。 - **云同步**是所有构建均可用的实验性「系统」目的地:导轨行、页面和设置搜索命中 @@ -319,6 +322,22 @@ Token 用量**不是设置目的地**(D335 / ADR 0173)。已完成回合历 - 激活项目或项目会话返回聊天;归档和关闭仍使项目归档页保持打开 - 明确打开已归档项目会清除渲染层归档状态,使其重新显示在主页侧边栏;仅选择详情行进行检查不会恢复项目 +### 索引库(`index` 选项卡,`工作区` 分组) +- 单张健康卡展示 host 所有的工作区索引缓存:状态、已索引文件数、已索引体积、 + 无法读取的文件数、最近更新时间 +- 健康卡下方有一个独立的「代码库」小节,承载唯一的开关「工作区索引」 + (`indexGrepBoost`,默认关)。开启后,切换到不同工作区会把其索引标记为 `building` + 并在阻塞线程池中重建,因此 `workspace.set` 保持快速;健康卡在构建期间每秒轮询 + 一次 `index.status`,窗口隐藏时暂停。任何工具都不读取该索引:它只在本页被查询、 + 重建与清理。再加一个「索引新文件夹」开关要么与它重复,要么建出无人使用的索引, + 因此该小节只保留一个 +- 操作为「重建索引」与「清理索引」。两者都调用 host 生命周期 RPC 并用返回状态刷新 + 卡片。重建只在开关打开时提供——开关不喂它的话,索引就只是白扫一遍盘、白占一份盘; + 清理始终可用,好让遗留索引仍能被删掉 +- 文案说明索引是可重建的本地缓存,数据不会离开本机 +- 空态:当前工作区还没有 root,只提供「建立索引」动作,并受同一条开关门控。 + 加载失败显示重试而不是空白卡片 + ### 云同步 - **Connection**: S3 endpoint, region, private bucket, prefix, access key ID, @@ -387,8 +406,8 @@ Token 用量**不是设置目的地**(D335 / ADR 0173)。已完成回合历 1.打开设置隐藏编码应用侧边栏(全页接管) 2. 导轨顶部显示搜索药丸,底部固定返回应用程序操作并与主侧边栏底部图标行同一条线, 并精确显示常规、AI、快捷键、指令、模型、技能、MCP、 - 子智能体、项目、云同步、远程主机和信息,按此顺序排列;云同步与远程主机 - 均在打包版中显示实验性标签,并按偏好、智能体、工作区、系统分组。 + 子智能体、项目、索引、云同步、远程主机和信息,按此顺序排列;云同步与 + 远程主机均在打包版中显示实验性标签,并按偏好、智能体、工作区、系统分组。 没有用量设置目的地。 3.外观是常规的一部分,没有独立的导轨目的地 4. Providers 是 Agent 的一部分,没有独立的导轨目的地 diff --git a/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md b/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md index a399bccf76..f7ff46ca64 100644 --- a/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md +++ b/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md @@ -993,6 +993,49 @@ task-candidate E2E 从请求工作树运行,但使用主工作区已经准备 - **里程碑**:M2 - **状态**:草案 +### 工作区索引 + +#### E2E-INDEX-status-rebuild-clear:隔离索引生命周期仅限当前工作区 + +- **前置条件**:debug host-core 可用;测试框架可创建临时 data、workspace 和 outside 目录。 +- **步骤**:1)以隔离的 `PI_DESKTOP_DATA_DIR` 启动 host-core。2)设置临时工作区。 + 3)确认 `index.status` 为空。4)调用 `index.rebuild`,确认索引一个 fixture 文件。 + 5)尝试重建工作区外目录。6)清理工作区 root,再次读取状态。 +- **预期**:重建产生一个 `fresh` root;状态仅暴露元数据、不暴露文件内容; + 工作区外 root 返回 `INDEX_ROOT_OUTSIDE_WORKSPACE`;clear 只删除所选 root,状态随后为空。 + 所有索引文件都位于临时 data 目录并由测试框架清理。 +- **关联规格**:`03-runtime/04-data-storage.md`、`03-runtime/06-host-rpc-protocol.md` +- **验收**:C(工作区工具与边界)、质量(数据安全) +- **里程碑**:M6+ +- **状态**:由 `pnpm test:e2e:index` 自动化 + +#### E2E-INDEX-settings-health-card:索引健康卡反映 host 生命周期 + +- **前置条件**:应用正在运行且存在活动工作区;设置的工作区分组下有「索引」目的地。 +- **步骤**:1)打开 设置 → 索引。2)观察无索引工作区的空态。3)点击「建立索引」 + 并等待卡片刷新。4)点击「清理索引」。5)可选:把窗口指向超预算或部分失败的索引工作区。 +- **预期**:卡片显示 host 返回的状态、已索引文件数、已索引体积、非零时的无法读取文件数 + 与最近更新时间。建立与清理都会调用 host 生命周期 RPC 并用返回状态刷新。文案把索引 + 描述为可重建的本地缓存,绝不声称 Grep 读取它。加载失败显示重试而不是空白卡片。 +- **关联规格**:`04-ux/06-settings-ia.md`、`03-runtime/06-host-rpc-protocol.md`、 + `03-runtime/04-data-storage.md` +- **验收**:D(工作区)、质量(本地数据安全) +- **里程碑**:M6+ +- **状态**:已记录;RPC 生命周期由 `pnpm test:e2e:index` 覆盖,UI 自动化待补 + +#### E2E-INDEX-auto-index:仅在开关开启时随工作区切换建立索引 + +- **前置条件**:host RPC 可用;两个临时工作区;开关为默认值。 +- **步骤**:1)开关关闭时 `workspace.set` 到工作区 A 并读取 `index.status`。 + 2)开启 `indexGrepBoost`。3)`workspace.set` 到工作区 B 并轮询 `index.status`。 +- **预期**:开关关闭时状态保持为空。开关开启后,变更的工作区立即标记 `building`, + 后台重建最终落到 `fresh` 并统计到 fixture 文件;`workspace.set` 立即返回、不等待扫描。 +- **关联规格**:`03-runtime/06-host-rpc-protocol.md`、`04-ux/06-settings-ia.md` +- **验收**:D(工作区)、质量(响应性) +- **里程碑**:M6+ +- **状态**:host-core RPC 边界已自动化 + (`workspace_set_auto_indexes_only_while_the_switch_is_on`) + ### 工作区打开 #### E2E-012:打开项目目录 diff --git a/package.json b/package.json index 2efe64d370..d1b3d777ba 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "test:host": "cargo test -p host-core", "test:e2e": "node scripts/e2e-smoke.mjs", "test:e2e:jev": "node --test apps/desktop/test/jev-api-key-check.test.mjs apps/desktop/test/jev-config.test.mjs apps/desktop/test/jev-settings-user-path.test.mjs apps/desktop/test/jev-service-setup-user-path.test.mjs apps/desktop/test/jev-session-launch.test.mjs && node packages/agent-runtime/node_modules/vitest/vitest.mjs run packages/agent-runtime/src/jev-classifier-tool.test.ts packages/agent-runtime/src/runtime.test.ts -t Jev --config packages/agent-runtime/vitest.config.ts", + "test:e2e:index": "node scripts/e2e-index.mjs", "test:e2e:storage": "node scripts/e2e-storage-settings.mjs && node scripts/e2e-storage-migration.mjs && node scripts/e2e-storage-bootstrap.mjs && node scripts/e2e-storage-directory.mjs && node scripts/e2e-storage-files.mjs && node scripts/e2e-storage-backup-lock.mjs", "test:e2e:asktool-card": "node scripts/e2e-asktool-card.mjs", "test:e2e:worktree-live": "node scripts/e2e-worktree-live.mjs", diff --git a/packages/i18n/src/locales/de/index.ts b/packages/i18n/src/locales/de/index.ts index 0fa00141cb..0d9256b23b 100644 --- a/packages/i18n/src/locales/de/index.ts +++ b/packages/i18n/src/locales/de/index.ts @@ -926,6 +926,7 @@ sklm: { "mcp": "MCP", "subagents": "Subagenten", "projects": "Projekte", + "index": "Index", "sync": "Cloud-Synchronisierung", "remoteHosts": "Remote-Hosts", "info": "Informationen", @@ -1322,6 +1323,7 @@ sklm: { "subagentCreated": "Erstellt {{name}}", "subagentSaved": "Gespeichert {{name}}", "projectArchive": "Projektarchiv", + "index": "Indexbibliothek", "remoteHosts": { "title": "Remote-Hosts", "listError": "Hosts konnten nicht geladen werden", @@ -2821,7 +2823,53 @@ sklm: { "continue": "Weiter", "dismiss": "Verwerfen" } - } + }, + "index": { + "loading": "Index-Status wird geladen…", + "grepBoost": "Workspace-Indizierung", + "grepBoostDesc": "Solange dieser Schalter aktiviert ist, werden neu geöffnete Workspaces im Hintergrund indexiert, und diese Seite zeigt deren Status. Alle Daten bleiben auf diesem Computer.", + "loadErrorTitle": "Index-Status nicht verfügbar", + "loadErrorDesc": "Der Host hat nicht geantwortet.", + "retry": "Erneut versuchen", + "statusDesc": "Der Index ist ein lokal neu aufbaubarer Zwischenspeicher.", + "indexSubtitle": "Der lokale Index ist ein neu aufbaubarer Zwischenspeicher; diese Seite zeigt Status und Zustand.", + "nudgeText": "Alles läuft lokal und lädt niemals Workspace-Inhalte hoch.", + "nudgeDismiss": "Verstanden", + "sectionCode": "Codebasis", + "progressFiles": "{{done}} / {{total}} Dateien", + "progressFallback": "Der Index wird im Hintergrund aufgebaut; Sie können weiterarbeiten.", + "emptyTitle": "Kein Index für den aktuellen Arbeitsbereich", + "emptyDesc": "Erstellen Sie einen, um Dateianzahl und Größe zu sehen. Der Index bleibt auf diesem Computer.", + "actions": "Index-Aktionen", + "actionsDesc": "Erstellt oder aktualisiert den Index des aktuellen Workspaces. Bei deaktiviertem Schalter ist das nicht möglich, weil nichts ihn verwenden würde.", + "localOnly": "Alles wird lokal gespeichert und nie hochgeladen.", + "rebuilding": "Wird indiziert…", + "clearing": "Wird gelöscht…", + "actionError": "Die Indexaktion ist fehlgeschlagen. Sie können es erneut versuchen.", + "card": { + "health": "Index-Status", + "status": "Status", + "files": "Indizierte Dateien", + "size": "Indexgröße", + "errors": "Unlesbare Dateien", + "updated": "Zuletzt aktualisiert", + }, + "action": { + "rebuild": "Index neu aufbauen", + "build": "Index aufbauen", + "clear": "Index löschen", + }, + "status": { + "fresh": "Bereit", + "building": "Wird aufgebaut", + "stale": "Aktualisiert", + "failed": "Fehlgeschlagen", + "partial": "Teilweise", + "disabled": "Aus", + "skipped_over_limit": "Budget überschritten", + }, + }, } satisfies EnglishCatalog; + export default de; diff --git a/packages/i18n/src/locales/en/index.ts b/packages/i18n/src/locales/en/index.ts index 7b16fca985..83afb85d10 100644 --- a/packages/i18n/src/locales/en/index.ts +++ b/packages/i18n/src/locales/en/index.ts @@ -938,6 +938,7 @@ sklm: { mcp: "MCP", subagents: "Subagents", projects: "Projects", + index: "Index", sync: "Cloud sync", remoteHosts: "Remote hosts", info: "Info", @@ -1174,6 +1175,7 @@ sklm: { subagentCreated: "Created {{name}}", subagentSaved: "Saved {{name}}", projectArchive: "Project archive", + index: "Index library", remoteHosts: { title: "Remote hosts", listError: "Failed to load hosts", @@ -2962,6 +2964,51 @@ importConfirm: "Imported extensions run inside the agent process with the same a dismiss: "Dismiss", }, }, + index: { + loading: "Loading index status…", + grepBoost: "Workspace indexing", + grepBoostDesc: "While this switch is on, newly opened workspaces are indexed in the background and this page reports their status. Everything stays on this machine.", + loadErrorTitle: "Index status unavailable", + loadErrorDesc: "The host did not answer.", + retry: "Retry", + statusDesc: "The index is a rebuildable local cache.", + indexSubtitle: "The local index is a rebuildable cache; this page reports its status and health.", + nudgeText: "Everything runs locally and never uploads your workspace content.", + nudgeDismiss: "Dismiss", + sectionCode: "Codebase", + progressFiles: "{{done}} / {{total}} files", + progressFallback: "The index builds in the background; you can keep working.", + emptyTitle: "No index for the current workspace", + emptyDesc: "Build one to see file counts and size. The index stays on this machine.", + actions: "Index actions", + actionsDesc: "Build or rebuild the index for the current workspace. It cannot be built while the switch is off, because nothing would use it.", + localOnly: "Everything is stored locally and never uploaded.", + rebuilding: "Indexing…", + clearing: "Clearing…", + actionError: "The index action failed. You can retry.", + card: { + health: "Index health", + status: "Status", + files: "Indexed files", + size: "Indexed size", + errors: "Unreadable files", + updated: "Last updated", + }, + action: { + rebuild: "Rebuild index", + build: "Build index", + clear: "Clear index", + }, + status: { + fresh: "Ready", + building: "Building", + stale: "Updating", + failed: "Failed", + partial: "Partial", + disabled: "Off", + skipped_over_limit: "Over budget", + }, + }, } as const; type DeepStringify = { diff --git a/packages/i18n/src/locales/es/index.ts b/packages/i18n/src/locales/es/index.ts index 205a5595ad..fd081b5d51 100644 --- a/packages/i18n/src/locales/es/index.ts +++ b/packages/i18n/src/locales/es/index.ts @@ -926,6 +926,7 @@ sklm: { "mcp": "MCP", "subagents": "Subagentes", "projects": "Proyectos", + "index": "Índice", "sync": "Sincronización en la nube", "remoteHosts": "Hosts remotos", "info": "Información", @@ -1322,6 +1323,7 @@ sklm: { "subagentCreated": "Creado {{name}}", "subagentSaved": "Guardado {{name}}", "projectArchive": "Archivo de proyecto", + "index": "Biblioteca de índices", "remoteHosts": { "title": "Hosts remotos", "listError": "No se pudieron cargar los hosts", @@ -2819,7 +2821,53 @@ sklm: { "continue": "Continuar", "dismiss": "Descartar" } - } + }, + "index": { + "loading": "Cargando estado del índice…", + "grepBoost": "Indexación del espacio de trabajo", + "grepBoostDesc": "Mientras este interruptor esté activado, los espacios de trabajo recién abiertos se indexan en segundo plano y esta página muestra su estado. Todo se queda en este equipo.", + "loadErrorTitle": "Estado del índice no disponible", + "loadErrorDesc": "El host no respondió.", + "retry": "Reintentar", + "statusDesc": "El índice es una caché local reconstruible.", + "indexSubtitle": "El índice local es una caché reconstruible; esta página muestra su estado y salud.", + "nudgeText": "Todo se ejecuta localmente y nunca sube el contenido de tu espacio de trabajo.", + "nudgeDismiss": "Entendido", + "sectionCode": "Base de código", + "progressFiles": "{{done}} / {{total}} archivos", + "progressFallback": "El índice se construye en segundo plano; puede seguir trabajando.", + "emptyTitle": "Sin índice para el espacio actual", + "emptyDesc": "Cree uno para ver el número de archivos y el tamaño. El índice se queda en este equipo.", + "actions": "Acciones del índice", + "actionsDesc": "Crea o reconstruye el índice del espacio de trabajo actual. No se puede crear con el interruptor desactivado, porque nada lo usaría.", + "localOnly": "Todo se guarda localmente y nunca se sube.", + "rebuilding": "Indexando…", + "clearing": "Borrando…", + "actionError": "La acción del índice falló. Puede volver a intentarlo.", + "card": { + "health": "Estado del índice", + "status": "Estado", + "files": "Archivos indexados", + "size": "Tamaño indexado", + "errors": "Archivos ilegibles", + "updated": "Última actualización", + }, + "action": { + "rebuild": "Reconstruir índice", + "build": "Crear índice", + "clear": "Borrar índice", + }, + "status": { + "fresh": "Listo", + "building": "Construyendo", + "stale": "Actualizando", + "failed": "Error", + "partial": "Parcial", + "disabled": "Desactivado", + "skipped_over_limit": "Presupuesto excedido", + }, + }, } satisfies EnglishCatalog; + export default es; diff --git a/packages/i18n/src/locales/fr/index.ts b/packages/i18n/src/locales/fr/index.ts index f015a56042..9db82593b0 100644 --- a/packages/i18n/src/locales/fr/index.ts +++ b/packages/i18n/src/locales/fr/index.ts @@ -926,6 +926,7 @@ sklm: { "mcp": "MCP", "subagents": "Sous-agents", "projects": "Projets", + "index": "Index", "sync": "Synchronisation cloud", "remoteHosts": "Hôtes distants", "info": "Informations", @@ -1322,6 +1323,7 @@ sklm: { "subagentCreated": "Créé {{name}}", "subagentSaved": "Enregistré {{name}}", "projectArchive": "Archive du projet", + "index": "Bibliothèque d'index", "remoteHosts": { "title": "Hôtes distants", "listError": "Impossible de charger les hôtes", @@ -2819,7 +2821,53 @@ sklm: { "continue": "Continuer", "dismiss": "Ignorer" } - } + }, + "index": { + "loading": "Chargement de l'état de l'index…", + "grepBoost": "Indexation de l'espace de travail", + "grepBoostDesc": "Lorsque cet interrupteur est activé, les espaces de travail récemment ouverts sont indexés en arrière-plan et cette page en affiche l'état. Tout reste sur cette machine.", + "loadErrorTitle": "État de l'index indisponible", + "loadErrorDesc": "L'hôte n'a pas répondu.", + "retry": "Réessayer", + "statusDesc": "L'index est un cache local reconstruisible.", + "indexSubtitle": "L'index local est un cache reconstruisible ; cette page en affiche l'état et la santé.", + "nudgeText": "Tout s'exécute localement et ne téléverse jamais le contenu de votre espace de travail.", + "nudgeDismiss": "Compris", + "sectionCode": "Base de code", + "progressFiles": "{{done}} / {{total}} fichiers", + "progressFallback": "L'index se construit en arrière-plan ; vous pouvez continuer à travailler.", + "emptyTitle": "Aucun index pour l'espace actuel", + "emptyDesc": "Créez-en un pour voir le nombre de fichiers et la taille. L'index reste sur cette machine.", + "actions": "Actions de l'index", + "actionsDesc": "Crée ou reconstruit l'index de l'espace de travail actuel. Impossible de le créer interrupteur désactivé, car rien ne l'utiliserait.", + "localOnly": "Tout est stocké localement et jamais envoyé.", + "rebuilding": "Indexation…", + "clearing": "Effacement…", + "actionError": "L'action d'index a échoué. Vous pouvez réessayer.", + "card": { + "health": "État de l'index", + "status": "État", + "files": "Fichiers indexés", + "size": "Taille indexée", + "errors": "Fichiers illisibles", + "updated": "Dernière mise à jour", + }, + "action": { + "rebuild": "Reconstruire l'index", + "build": "Créer l'index", + "clear": "Effacer l'index", + }, + "status": { + "fresh": "Prêt", + "building": "Construction", + "stale": "Mise à jour", + "failed": "Échec", + "partial": "Partiel", + "disabled": "Désactivé", + "skipped_over_limit": "Budget dépassé", + }, + }, } satisfies EnglishCatalog; + export default fr; diff --git a/packages/i18n/src/locales/ko/index.ts b/packages/i18n/src/locales/ko/index.ts index 5c5b1723f9..c0d73146be 100644 --- a/packages/i18n/src/locales/ko/index.ts +++ b/packages/i18n/src/locales/ko/index.ts @@ -934,6 +934,7 @@ sklm: { mcp: "MCP", subagents: "서브에이전트", projects: "프로젝트", + index: "인덱스", sync: "클라우드 동기화", remoteHosts: "원격 호스트", info: "정보", @@ -1330,6 +1331,7 @@ sklm: { subagentCreated: "{{name}} 생성됨", subagentSaved: "{{name}} 저장됨", projectArchive: "프로젝트 보관함", + index: "인덱스 라이브러리", remoteHosts: { title: "원격 호스트", listError: "호스트를 불러오지 못했습니다", @@ -2861,6 +2863,51 @@ importConfirm: "가져온 확장은 에이전트 프로세스 안에서 에이 dismiss: "닫기", }, }, + index: { + loading: "인덱스 상태 불러오는 중…", + "grepBoost": "워크스페이스 색인", + "grepBoostDesc": "이 스위치가 켜져 있으면 새로 여는 워크스페이스가 백그라운드에서 색인되고 이 페이지에 그 상태가 표시됩니다. 모든 데이터는 이 컴퓨터에만 저장됩니다.", + loadErrorTitle: "인덱스 상태를 사용할 수 없음", + loadErrorDesc: "호스트가 응답하지 않습니다.", + retry: "다시 시도", + statusDesc: "색인은 다시 생성할 수 있는 로컬 캐시입니다.", + indexSubtitle: "로컬 색인은 다시 생성할 수 있는 캐시입니다. 이 페이지는 그 상태와 건강도를 표시합니다.", + nudgeText: "모든 처리는 로컬에서 이루어지며 워크스페이스 콘텐츠를 업로드하지 않습니다.", + nudgeDismiss: "확인", + sectionCode: "코드베이스", + progressFiles: "{{done}} / {{total}}개 파일", + progressFallback: "색인은 백그라운드에서 생성되며, 그동안 작업을 계속할 수 있습니다.", + emptyTitle: "현재 워크스페이스에 인덱스가 없습니다", + emptyDesc: "생성하면 파일 수와 크기를 볼 수 있습니다. 색인은 이 컴퓨터에만 저장됩니다.", + actions: "인덱스 작업", + actionsDesc: "현재 워크스페이스의 색인을 생성하거나 다시 생성합니다. 스위치가 꺼져 있으면 사용하는 기능이 없으므로 생성할 수 없습니다.", + localOnly: "모든 데이터는 로컬에 저장되며 업로드되지 않습니다.", + rebuilding: "색인 중…", + clearing: "삭제 중…", + actionError: "색인 작업이 실패했습니다. 다시 시도할 수 있습니다.", + card: { + health: "인덱스 상태", + status: "상태", + files: "색인된 파일", + size: "색인 크기", + errors: "읽을 수 없는 파일", + updated: "마지막 업데이트", + }, + action: { + rebuild: "인덱스 재생성", + build: "인덱스 생성", + clear: "인덱스 삭제", + }, + status: { + fresh: "준비됨", + building: "생성 중", + stale: "업데이트 중", + failed: "실패", + partial: "일부 실패", + disabled: "꺼짐", + skipped_over_limit: "예산 초과", + }, + }, } satisfies EnglishCatalog; export default ko; diff --git a/packages/i18n/src/locales/pt-BR/index.ts b/packages/i18n/src/locales/pt-BR/index.ts index 48036f4236..3375376e9d 100644 --- a/packages/i18n/src/locales/pt-BR/index.ts +++ b/packages/i18n/src/locales/pt-BR/index.ts @@ -912,6 +912,7 @@ export const ptBR = { mcp: "MCP", subagents: "Subagentes", projects: "Projetos", + index: "Índice", sync: "Sincronização", remoteHosts: "Hosts remotos", info: "Sobre", @@ -1142,6 +1143,7 @@ export const ptBR = { subagentCreated: "{{name}} criado", subagentSaved: "Salvo {{name}}", projectArchive: "Arquivo de projeto", + index: "Biblioteca de índices", remoteHosts: { title: "Hosts remotos", listError: "Falha ao carregar hosts", @@ -2789,6 +2791,51 @@ export const ptBR = { continue: "Continuar", dismiss: "Dispensar" } + }, + index: { + loading: "Carregando status do índice…", + grepBoost: "Indexação do espaço de trabalho", + grepBoostDesc: "Com esta opção ativada, os espaços de trabalho recém-abertos são indexados em segundo plano e esta página informa o status. Tudo permanece nesta máquina.", + loadErrorTitle: "Status do índice indisponível", + loadErrorDesc: "O host não respondeu.", + retry: "Tentar novamente", + statusDesc: "O índice é um cache local reconstruível.", + indexSubtitle: "O índice local é um cache reconstruível; esta página informa o status e a saúde dele.", + nudgeText: "Tudo é executado localmente e nunca envia o conteúdo do seu espaço de trabalho.", + nudgeDismiss: "Dispensar", + sectionCode: "Base de código", + progressFiles: "{{done}} / {{total}} arquivos", + progressFallback: "O índice é construído em segundo plano; você pode continuar trabalhando.", + emptyTitle: "Nenhum índice para o espaço de trabalho atual", + emptyDesc: "Construa um para ver a contagem de arquivos e o tamanho. O índice permanece nesta máquina.", + actions: "Ações do índice", + actionsDesc: "Construa ou reconstrua o índice do espaço de trabalho atual. Ele não pode ser construído com a opção desativada, pois nada o utilizaria.", + localOnly: "Tudo é armazenado localmente e nunca enviado.", + rebuilding: "Indexando…", + clearing: "Limpando…", + actionError: "A ação do índice falhou. Você pode tentar novamente.", + card: { + health: "Saúde do índice", + status: "Status", + files: "Arquivos indexados", + size: "Tamanho indexado", + errors: "Arquivos ilegíveis", + updated: "Última atualização" + }, + action: { + rebuild: "Reconstruir índice", + build: "Construir índice", + clear: "Limpar índice" + }, + status: { + fresh: "Pronto", + building: "Construindo", + stale: "Atualizando", + failed: "Falhou", + partial: "Parcial", + disabled: "Desativado", + skipped_over_limit: "Acima do orçamento" + } } } satisfies EnglishCatalog; diff --git a/packages/i18n/src/locales/tr/index.ts b/packages/i18n/src/locales/tr/index.ts index 10d7331cff..b0113d237d 100644 --- a/packages/i18n/src/locales/tr/index.ts +++ b/packages/i18n/src/locales/tr/index.ts @@ -934,6 +934,7 @@ sklm: { mcp: "MCP", subagents: "Alt ajanlar", projects: "Projeler", + index: "Dizin", sync: "Bulut senkronizasyonu", remoteHosts: "Uzak ana bilgisayarlar", info: "Bilgi", @@ -1324,6 +1325,7 @@ sklm: { subagentCreated: "{{name}} oluşturuldu", subagentSaved: "{{name}} kaydedildi", projectArchive: "Proje arşivi", + index: "Dizin kitaplığı", remoteHosts: { title: "Uzak ana bilgisayarlar", listError: "Ana bilgisayarlar yüklenemedi", @@ -2855,6 +2857,51 @@ importConfirm: "İçe aktarılan uzantılar ajan sürecinde, ajanın kendi araç dismiss: "Kapat", }, }, + index: { + loading: "Dizin durumu yükleniyor…", + "grepBoost": "Çalışma alanı dizinleme", + "grepBoostDesc": "Bu anahtar açıkken yeni açılan çalışma alanları arka planda dizinlenir ve bu sayfa durumlarını gösterir. Her şey bu makinede kalır.", + loadErrorTitle: "Dizin durumu kullanılamıyor", + loadErrorDesc: "Ana makine yanıt vermedi.", + retry: "Yeniden dene", + statusDesc: "Dizin yeniden oluşturulabilen yerel bir önbellektir.", + indexSubtitle: "Yerel dizin yeniden oluşturulabilen bir önbellektir; bu sayfa durumunu ve sağlığını gösterir.", + nudgeText: "Her şey yerel olarak çalışır ve çalışma alanı içeriğinizi asla yüklemez.", + nudgeDismiss: "Anladım", + sectionCode: "Kod tabanı", + progressFiles: "{{done}} / {{total}} dosya", + progressFallback: "Dizin arka planda oluşturulur; çalışmaya devam edebilirsiniz.", + emptyTitle: "Geçerli çalışma alanı için dizin yok", + emptyDesc: "Dosya sayısını ve boyutu görmek için bir tane oluşturun. Dizin bu makinede kalır.", + actions: "Dizin işlemleri", + actionsDesc: "Geçerli çalışma alanının dizinini oluşturur veya yeniden oluşturur. Anahtar kapalıyken oluşturulamaz, çünkü hiçbir şey onu kullanmazdı.", + localOnly: "Her şey yerel saklanır, asla yüklenmez.", + rebuilding: "Dizinleniyor…", + clearing: "Temizleniyor…", + actionError: "Dizin eylemi başarısız oldu. Yeniden deneyebilirsiniz.", + card: { + health: "Dizin durumu", + status: "Durum", + files: "Dizinlenen dosyalar", + size: "Dizin boyutu", + errors: "Okunamayan dosyalar", + updated: "Son güncelleme", + }, + action: { + rebuild: "Dizini yeniden oluştur", + build: "Dizin oluştur", + clear: "Dizini temizle", + }, + status: { + fresh: "Hazır", + building: "Oluşturuluyor", + stale: "Güncelleniyor", + failed: "Başarısız", + partial: "Kısmi", + disabled: "Kapalı", + skipped_over_limit: "Bütçe aşıldı", + }, + }, } satisfies EnglishCatalog; export default tr; diff --git a/packages/i18n/src/locales/zh-CN/index.ts b/packages/i18n/src/locales/zh-CN/index.ts index b193bfda29..7b676a609e 100644 --- a/packages/i18n/src/locales/zh-CN/index.ts +++ b/packages/i18n/src/locales/zh-CN/index.ts @@ -925,6 +925,7 @@ sklm: { mcp: "MCP", subagents: "子智能体", projects: "项目", + index: "索引", sync: "云同步", remoteHosts: "远程主机", info: "信息", @@ -1314,6 +1315,7 @@ sklm: { subagentCreated: "已新建 {{name}}", subagentSaved: "已保存 {{name}}", projectArchive: "项目归档", + index: "索引库", remoteHosts: { title: "远程主机", listError: "加载失败", @@ -2896,6 +2898,51 @@ sklm: { dismiss: "关闭", }, }, + index: { + loading: "正在读取索引状态…", + "grepBoost": "工作区索引", + "grepBoostDesc": "开关打开时,新打开的工作区会在后台建立索引,本页显示其状态。所有数据均保存在本机。", + loadErrorTitle: "索引状态不可用", + loadErrorDesc: "主机未响应。", + retry: "重试", + statusDesc: "索引是可重建的本地缓存。", + indexSubtitle: "本地索引是可重建的缓存;本页显示其状态与健康度。", + nudgeText: "所有处理均在本地完成,不会上传工作区内容。", + nudgeDismiss: "知道了", + sectionCode: "代码库", + progressFiles: "{{done}} / {{total}} 个文件", + progressFallback: "索引在后台构建,期间可继续工作。", + emptyTitle: "当前工作区尚未建立索引", + emptyDesc: "建立索引后可查看文件数与体积。索引仅保存在本机。", + actions: "索引操作", + actionsDesc: "为当前工作区建立或重建索引。开关关闭时无法建立,因为没有功能会使用它。", + localOnly: "所有数据均存储在本地,不会上传。", + rebuilding: "正在索引…", + clearing: "正在清理…", + actionError: "索引操作失败。可以重试。", + card: { + health: "索引健康", + status: "状态", + files: "已索引文件", + size: "已索引体积", + errors: "无法读取的文件", + updated: "最近更新", + }, + action: { + rebuild: "重建索引", + build: "建立索引", + clear: "清理索引", + }, + status: { + fresh: "就绪", + building: "构建中", + stale: "更新中", + failed: "失败", + partial: "部分失败", + disabled: "已停用", + skipped_over_limit: "超出预算", + }, + }, } satisfies EnglishCatalog; export default zhCN; diff --git a/packages/i18n/src/locales/zh-TW/index.ts b/packages/i18n/src/locales/zh-TW/index.ts index a954bec613..e525226830 100644 --- a/packages/i18n/src/locales/zh-TW/index.ts +++ b/packages/i18n/src/locales/zh-TW/index.ts @@ -925,6 +925,7 @@ sklm: { mcp: "MCP", subagents: "子智慧體", projects: "專案", + index: "索引", sync: "雲端同步", remoteHosts: "遠端主機", info: "資訊", @@ -1314,6 +1315,7 @@ sklm: { subagentCreated: "已新建 {{name}}", subagentSaved: "已儲存 {{name}}", projectArchive: "專案歸檔", + index: "索引庫", remoteHosts: { title: "遠端主機", listError: "載入失敗", @@ -2846,6 +2848,51 @@ sklm: { dismiss: "關閉", }, }, + index: { + loading: "正在讀取索引狀態…", + "grepBoost": "工作區索引", + "grepBoostDesc": "開關開啟時,新開啟的工作區會在背景建立索引,本頁顯示其狀態。所有資料均儲存在本機。", + loadErrorTitle: "索引狀態不可用", + loadErrorDesc: "主機未回應。", + retry: "重試", + statusDesc: "索引是可重建的本機快取。", + indexSubtitle: "本機索引是可重建的快取;本頁顯示其狀態與健康度。", + nudgeText: "所有處理均在本機完成,不會上傳工作區內容。", + nudgeDismiss: "知道了", + sectionCode: "程式碼庫", + progressFiles: "{{done}} / {{total}} 個檔案", + progressFallback: "索引在背景建置,期間可繼續工作。", + emptyTitle: "目前工作區尚未建立索引", + emptyDesc: "建立索引後可查看檔案數與大小。索引僅儲存在本機。", + actions: "索引操作", + actionsDesc: "為目前工作區建立或重建索引。開關關閉時無法建立,因為沒有功能會使用它。", + localOnly: "所有資料均儲存在本機,不會上傳。", + rebuilding: "正在索引…", + clearing: "正在清除…", + actionError: "索引操作失敗。可以重試。", + card: { + health: "索引健康", + status: "狀態", + files: "已索引檔案", + size: "已索引大小", + errors: "無法讀取的檔案", + updated: "最近更新", + }, + action: { + rebuild: "重建索引", + build: "建立索引", + clear: "清除索引", + }, + status: { + fresh: "就緒", + building: "建置中", + stale: "更新中", + failed: "失敗", + partial: "部分失敗", + disabled: "已停用", + skipped_over_limit: "超出預算", + }, + }, } satisfies EnglishCatalog; export default zhTW; diff --git a/packages/shared/src/errors.ts b/packages/shared/src/errors.ts index b316de887a..592f58f84d 100644 --- a/packages/shared/src/errors.ts +++ b/packages/shared/src/errors.ts @@ -134,6 +134,12 @@ export const ErrorCodes = { */ SUBAGENT_CONTEXT_OVERFLOW: "SUBAGENT_CONTEXT_OVERFLOW", SUBAGENT_OUTPUT_TRUNCATED: "SUBAGENT_OUTPUT_TRUNCATED", + /** index.status/rebuild/clear could not reach the index store. */ + INDEX_UNAVAILABLE: "INDEX_UNAVAILABLE", + /** The requested index root does not match the active workspace root. */ + INDEX_ROOT_OUTSIDE_WORKSPACE: "INDEX_ROOT_OUTSIDE_WORKSPACE", + /** A background index rebuild failed; Grep keeps working via rg. */ + INDEX_REBUILD_FAILED: "INDEX_REBUILD_FAILED", WORKSPACE_REQUIRED: "WORKSPACE_REQUIRED", PATH_OUTSIDE_WORKSPACE: "PATH_OUTSIDE_WORKSPACE", TOOL_NOT_FOUND: "TOOL_NOT_FOUND", diff --git a/packages/shared/src/protocol.ts b/packages/shared/src/protocol.ts index 7635845243..c256eae899 100644 --- a/packages/shared/src/protocol.ts +++ b/packages/shared/src/protocol.ts @@ -414,6 +414,9 @@ export const IPC = { fsRead: "pi-desktop/fs/read", fsReadImageDataUrl: "pi-desktop/fs/readImageDataUrl", statsGetTokenUsageHistory: "pi-desktop/stats/getTokenUsageHistory", + indexStatus: "pi-desktop/index/status", + indexRebuild: "pi-desktop/index/rebuild", + indexClear: "pi-desktop/index/clear", fsReveal: "pi-desktop/fs/reveal", fsOpen: "pi-desktop/fs/open", fsIndex: "pi-desktop/fs/index", diff --git a/packages/shared/src/types.ts b/packages/shared/src/types.ts index a3bfbfef93..94c9fc9ee1 100644 --- a/packages/shared/src/types.ts +++ b/packages/shared/src/types.ts @@ -10,6 +10,7 @@ export * from "./types/permissions.js"; export * from "./types/messages.js"; export * from "./types/sessions.js"; export * from "./types/worktree.js"; +export * from "./types/workspace-index.js"; export * from "./types/agent.js"; export * from "./types/workspace.js"; export * from "./types/providers.js"; diff --git a/packages/shared/src/types/settings.ts b/packages/shared/src/types/settings.ts index 26d6d96cb4..8af4019562 100644 --- a/packages/shared/src/types/settings.ts +++ b/packages/shared/src/types/settings.ts @@ -38,6 +38,12 @@ export type JevKeyCheckResult = { export type CloseBehavior = "ask" | "tray" | "quit"; export type AppSettings = { + /** + * Workspace group. One switch, default off: it owns the whole lifecycle of + * the workspace index — on, newly opened workspaces get indexed in the + * background so the Index page can report status; off, nothing builds one. + */ + indexGrepBoost: boolean; imageGeneration?: import("../image-generation.js").ImageGenerationBinding | null; /** All models marked for image generation; absent falls back to imageGeneration. */ imageGenerationModels?: import("../image-generation.js").ImageGenerationBinding[] | null; diff --git a/packages/shared/src/types/workspace-index.ts b/packages/shared/src/types/workspace-index.ts new file mode 100644 index 0000000000..771625d156 --- /dev/null +++ b/packages/shared/src/types/workspace-index.ts @@ -0,0 +1,26 @@ +/** + * Workspace index status types (`index.status`). These describe the index — + * a rebuildable local cache. + */ + +export type WorkspaceIndexRootStatus = + | "fresh" + | "building" + | "stale" + | "failed" + | "partial" + | "disabled" + | "skipped_over_limit"; + +export type WorkspaceIndexRoot = { + rootId: string; + rootPath: string; + status: WorkspaceIndexRootStatus; + fileCount: number; + indexedBytes: number; + errorCount: number; + lastError: string | null; + updatedAt: number; + /** Present while status is "building"; absent otherwise. */ + progress?: { filesDone: number; filesTotal: number }; +}; diff --git a/scripts/README.md b/scripts/README.md index e2dba3b883..cc6ca9f0dd 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -54,6 +54,7 @@ The scenarios are specified in |---|---|---| | `e2e-tool-admission.mjs` | `node scripts/e2e-tool-admission.mjs` | Isolated real-host tool admission: queued Bash burst leaves capacity for another session's Read/Write and recovers all counters | | `e2e-smoke.mjs` | `pnpm test:e2e` | Protocol-level E2E against host-core, plus an optional live model | +| `e2e-index.mjs` | `pnpm test:e2e:index` | Isolated workspace-index status, rebuild, boundary rejection, and clear lifecycle | | `e2e-plan.mjs` | `pnpm test:e2e:plan` | Plan state, checkpoint artifact, and approval transitions | | `e2e-transcript-cache.mjs` | `pnpm test:e2e:transcript-cache` | Isolated Host RPC regression for large transcript cache creation, append plus middle Task rewrite, legacy cache recovery, paging, message navigation, invalid UTF-8 recovery, and process restart | | `e2e-plan-ui.mjs` | `pnpm test:e2e:plan-ui` | Plan approval through the rendered UI | diff --git a/scripts/dev-electron.mjs b/scripts/dev-electron.mjs index b6272f6fb6..2a7f583bca 100644 --- a/scripts/dev-electron.mjs +++ b/scripts/dev-electron.mjs @@ -152,8 +152,11 @@ export function prepareMacDevelopmentBundle({ function run() { const env = { ...process.env, PI_DESKTOP_DEV: "1" }; - // Electron 43+ downloads its platform binary when its package is resolved. - // electron-vite requires the resulting path.txt marker on every platform. + // Resolving the electron package is what triggers the platform binary + // download (and path.txt) on a fresh clone — electron-vite only reads + // path.txt and never triggers the download itself (#1144). Resolve on + // every platform; macOS additionally reuses the resolved executable for + // the branded development bundle. const electron = resolveElectronInstallation(); if (process.platform === "darwin") { env.ELECTRON_EXEC_PATH = prepareMacDevelopmentBundle({ diff --git a/scripts/e2e-index.mjs b/scripts/e2e-index.mjs new file mode 100644 index 0000000000..dff4e01946 --- /dev/null +++ b/scripts/e2e-index.mjs @@ -0,0 +1,123 @@ +#!/usr/bin/env node +import { spawn } from "node:child_process"; +import { randomUUID } from "node:crypto"; +import { existsSync } from "node:fs"; +import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import { createInterface } from "node:readline"; +import { fileURLToPath } from "node:url"; + +const root = join(dirname(fileURLToPath(import.meta.url)), ".."); +const protocolVersion = 11; +const binary = resolveHostBinary(); +const scenarioRoot = await mkdtemp(join(tmpdir(), "pi-index-e2e-")); +const dataDir = join(scenarioRoot, "data"); +const workspace = join(scenarioRoot, "workspace"); +const outside = join(scenarioRoot, "outside"); +await mkdir(dataDir, { recursive: true }); +await mkdir(workspace, { recursive: true }); +await mkdir(outside, { recursive: true }); +await writeFile(join(workspace, "README.md"), "workspace index fixture\n", "utf8"); + +let child; +let lines; +const pending = new Map(); +let stderr = ""; + +try { + child = spawn(binary, [], { + stdio: ["pipe", "pipe", "pipe"], + env: { ...process.env, PI_DESKTOP_DATA_DIR: dataDir }, + windowsHide: true, + }); + child.stderr.on("data", (chunk) => { + stderr += String(chunk); + }); + lines = createInterface({ input: child.stdout }); + lines.on("line", (line) => { + const message = JSON.parse(line); + if (message.id === undefined || message.id === null) return; + const entry = pending.get(String(message.id)); + if (!entry) return; + pending.delete(String(message.id)); + clearTimeout(entry.timer); + if (message.error) { + const error = new Error(message.error.message); + error.errorCode = message.error.data?.errorCode; + entry.reject(error); + } else { + entry.resolve(message.result); + } + }); + + await call("app.handshake", { protocolVersion }); + await call("workspace.set", { path: workspace }); + + const before = await call("index.status"); + assert(Array.isArray(before.roots) && before.roots.length === 0, "new index must be empty"); + + const rebuilt = await call("index.rebuild"); + assert(rebuilt.root.status === "fresh", `expected fresh, got ${rebuilt.root.status}`); + assert(rebuilt.root.fileCount === 1, `expected one file, got ${rebuilt.root.fileCount}`); + + const status = await call("index.status", { rootPath: workspace }); + assert(status.roots.length === 1, "rebuilt root must be visible"); + assert(status.roots[0].indexedBytes > 0, "indexed byte count must be positive"); + + let outsideRejected = false; + try { + await call("index.rebuild", { rootPath: outside }); + } catch (error) { + outsideRejected = error.errorCode === "INDEX_ROOT_OUTSIDE_WORKSPACE"; + } + assert(outsideRejected, "index RPC must reject a root outside the active workspace"); + + const cleared = await call("index.clear", { rootPath: workspace }); + assert(cleared.ok && cleared.cleared === 1, "clear must remove exactly one root"); + const after = await call("index.status"); + assert(after.roots.length === 0, "cleared index must be empty"); + + console.log("PASS E2E-INDEX-status-rebuild-clear - isolated index lifecycle RPCs"); +} catch (error) { + console.error(`FAIL E2E-INDEX-status-rebuild-clear - ${error.stack || error}`); + if (stderr.trim()) console.error(stderr.trim().slice(-2000)); + process.exitCode = 1; +} finally { + for (const entry of pending.values()) { + clearTimeout(entry.timer); + entry.reject(new Error("host stopped")); + } + pending.clear(); + lines?.close(); + if (child && child.exitCode === null) child.kill(); + await rm(scenarioRoot, { recursive: true, force: true }); +} + +function call(method, params = {}, timeoutMs = 30_000) { + const id = randomUUID(); + return new Promise((resolveResult, rejectResult) => { + const timer = setTimeout(() => { + pending.delete(id); + rejectResult(new Error(`timeout ${method}`)); + }, timeoutMs); + pending.set(id, { resolve: resolveResult, reject: rejectResult, timer }); + child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}\n`); + }); +} + +function resolveHostBinary() { + const name = process.platform === "win32" ? "pi-desktop-host-core.exe" : "pi-desktop-host-core"; + const candidates = [ + process.env.PI_DESKTOP_HOST_BIN && resolve(process.env.PI_DESKTOP_HOST_BIN), + join(root, "target", "debug", name), + join(root, "..", "..", "..", "target", "debug", name), + ].filter(Boolean); + const found = candidates.find((candidate) => existsSync(candidate)); + if (!found) throw new Error(`host binary missing; tried ${candidates.join(", ")}`); + return found; +} + +function assert(condition, message) { + if (!condition) throw new Error(message); +} From cba17f973e8d58f6701032f54587db0348080e68 Mon Sep 17 00:00:00 2001 From: muzimu217 Date: Mon, 19 Oct 2026 21:45:02 -0700 Subject: [PATCH 2/3] docs: add workspace index settings screenshots for #697 review --- .pr-assets/index-settings-detail.png | Bin 0 -> 38162 bytes .pr-assets/index-settings-full.png | Bin 0 -> 206514 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 .pr-assets/index-settings-detail.png create mode 100644 .pr-assets/index-settings-full.png diff --git a/.pr-assets/index-settings-detail.png b/.pr-assets/index-settings-detail.png new file mode 100644 index 0000000000000000000000000000000000000000..6d376ba60dd4edfab5df693b74d007e640dc04ae GIT binary patch literal 38162 zcmeFZ^;=YH*gv`u5ep0i6$ESq1d(PCPytc8krt8e8amXiC-Z7)O?~0adHCV3nYg&3K7zPJhkh23A%ArCJ+t`3F5ZNMcXgp=T{mBfDxRX@GmX3> zfoq;T{_=cK^T!Rd{c}C&Hvz}ftul|YpJQ-;lpC-(OndS3g-8Q*jHh$o@Z}F2*ZfoC zhL?w=%S^lETZ; zPll5k56Di3$PHX6BcDc)v&b`vN2)H-^TU)L7rIC4mxD)6Tw5DS#;zs2B&$1?_Tl7* zi?r_Fd(k<%KLaNR+$k8;fBId0b3y5xgqifB$kC3u-P}Zn2DzvFjSI%-y+&L2=Z7PI zkO(^?mp5oy#S}=hOwa2?BvIIXUSzF)L{*DwqQ6B-&C8EJ;4mp+523Ux6e4?mYBt*OderUuL8 z<>lGg+2K1v3Gou0F}w(J@%Wj;3=6b`#6L{k8@Y!`FT-MDV!nKdr%|aNz9WJtswp}n z$fK!&4x0Hsc}ez>A5n#KbhM`s>{&IwIeKPXMY3yJ(&f$gHRPe1)W5eTBdwrlh>M7b zpyxCH=RMOUN^wbc9-iP13Pdf+X)I{HA};R5XqBfm&f_CIPxlEYO62UTP;Iw27YA?M zzFl8mPmjHbs0!ny!@hj^5*1}W^qO?p`Ts6cb9-}c5QgX0ty`Mmlp5{3zm6c%MjH7; zLqj1UAx)KYXOk%rtjH`ia`o36eYe_qcg~|9q&4jA^PO%j9qjLBH1rV>pM;f_mB)`C zJJx7)O0LUN?!9?)MCCF(t?iw-1q69>cPp7CHxHv$^H^Mb{d-4e=gci?r2CswFOlm^ zXx{w%{GXCI($&-dUHvPW#U~FV>Z4oZ*pZ%(_asB!Jd<+AI4UzdQM-ylq1f5kBMdo| z+>k$t;-nGM)iR=9^*#G=sMA+$vtzJVI^Gon=dvq@i|v%_(vaMEpBGE-rp1GwjGQM3EzRVe4$$>tR%@$t^5=T{F{5+;5AKi|P7a`1`x^5sjIu#kK9BPWnQuP~m<0gB07 zUXn9p$OXxN$J9qUNX!s7J#9MPuSky6h-r3z$6oM~AYI`9@Ao_mVe+m=D=8_t{cdT2 zy0RCn#+WHRCKbYjJk0xVXe4HqwJk4RzI^%o`Imz0N0B>$YK*F^s`oy{D!L&E<$sfR zRXS+&CGNwA<3BK(WC*26==}`6Ez*=I9{Kly_w+~)OorHUk(rBb z*RNlHEWR(aSFZB9N#5ETjYhkQbdwLKN+~FmK}*kcU27m1}V4r=oMGKcvws-Cjy9_H&(!_z*F@H1FBJtvOV4 z1fjmRyuAGE*|QYxpKeL;N=_#2hSxA#chuu{ba!PWbY7{xbWDDQ_C9f<{g^4wY1kZ! z@O4e*!u^G01EFFtE;=LVNZNdhOoUMJ!z*PT3svBm({_Jb|ZhVkm5cZaG#cXBk>C@viI` z;)7h`Ggobl#BAdjlk2Rjwx{&H_q$;clWojg7tVFC#~0Fl<2Czwt(M#en^|6uwpxE) zAZgzc*67|l&yOHm-@31B<-hvaeTNNAhNDK@@Fuyd&$Iylc6!NsE`yL>xxAQZ9i_a> zI_*MiYFJT%``JNgjA~YtCkAjE4_R<2EamrW{Xc#*>r9awE_V`teCf{-1S=-#uI_uB zuROCNXV$zUsqx)KJ(rGWESF1WyEDRzbNiYb{LBXmXD*vW+pTwN=sR_*_5AwS-rgRr zi|&uOZ&>@|(AnH!ZMt2{1Uf01PZO7U*PZbb^T(*K2-^LXU?vyjNO`w@KI5KVH^)OG zRIY~KHSzbRr@E_nXnV4>OTO7urDG2*$b6X9nn%!-*`MWfD|c%o^Eti-Sfq*APDPr` zp4Gvjtt{l#)q4htEJocID;b5+mta17S&+%%4%d?WwWp82{CR$dCU`lWCH2`!eRRh8Y(qXcY5@{O7^BZqO$ z8t1$T7-xG22P#DMdbTALn>eG@Ac1OFAlrJHV2M{-q5`m&9itL@9zJ2h?=OQTzfMDwmRyT)7Lh34JT zR;gBl#a5$LBKPy2ygf$M{a|ae`fz_|F6VG#&}O3+D)o4MKxZ&+p`2Y zXVb8lCoL_K$W$jFUt8=5C z5ga1$4Hnv?;bb z-+v%AqgY+b-dS&rI_oWC$rhO)6qiYPx=s@w<-Cx|VpF|ejOqVqUtTiemf^ZO6+q87 zJ5a<$5~a?}ZU3$F&w6Uzk@W10*d7he{AHl@W|#u(r|2(W~6r+S=RObDHa+ z-JfHysc0{?Gn^AW4rP)MbmcNVo$c*diHE* zfr*0W^KO5RZvyx9^ImSROm3jW#l>4icjx;eB4WJ`H0SK9mex8Iaxl?*tF`Z{4-ar6 z1)jSbo;zzwkuq{}x(j*mQ-Tfs#f#2+%MB&4h!s?mrdnfjZUxxveveVLD9(+$dV|Md zWukFvkvmFX@lW$`qQ?+Ej6>VJmDg&3myON7Go?k#vOPia7}8Yd>WJ#Ap;(w{8%~h) zH{4y zW;#>DXS~qK{5j>$izDfV4f=E(`MXM3pZbP6T3oR%&Uvm=h_LlM0#Uuz%~ou||EH!j zujf)eorv=fA`!hX6#4BN!?|KmTnufMu*R&tVFPnj8ZMF1`5ky zqX0@+ugJ~KeHMHh%SnFhw9COZ;SgGla&O#DYBfs3bgn1s+sP3Ym6X0bU0-TO&WfeS zl@jl6vh#G7U(9T(Id!k%g@z#FGaYB{Jg_qvt{?5aVlM2e%f%oI%z%vy_3LnJJg8UC zJqxS+=+SFzy@4-d%I%-yL1I-#&2;W#pFf`sW#G%pY8gFa@W#M} z39}RtGbfE+)@+JXbb_O*GlMMxqnVvb^(trAQ`lV1MI$@XzE9l^Q5Sf|X9IM}n`7x( z@v5D$0&3=F5kryxT6&QN6coF)cBwh36{vI$^rs(ElpT7VYHVyor9F|9n&T35oL00* ziK4}zoI1ufp#`I<*Hu!;aq_JDyQ7E2QIhpM+@<)~^vKm;H-es*Y{)-**8W{5ApDGfK z%)^)`*H*pMNb737Jh)Aob&hV9VikT9-D}^JJmLnBq)N4|VPF*miUY&9RW{qEa{|Hxx(_5Zg8G+8WSbeBxp-9pFku|jM(c5FB zGJFhKDluY8VN*19s-xBLSgyOs_Qbs7PLPU`5{;zP7E6pvt={}Zu!6dAs(EsLk>iZA z8W|ryUU2*qPwRt%X_VBbOYuE;=$vA(0AW|9>!nFL!WPzdoeVW!eO^iHwKe8@;{hHu zmkD*Y*=0e;3ok@4Z82Bs!`yJ&T*$gDrKJ`1m^W0OFF;jdo3U0jL8R3gn3=uVEN;oujR(6_pSW( zSTa5-XvR1ke7#6GRwxxCkJWc47GY_A_@3!)*KBCvv*_77*j<#o_f4&7%3~o~R$5y5 z%dcO*wx+(>WFC93o^SBuSW%`b(ly<&N?H{o*6S2>FqM?RkqoW-=p0UYbuNYa4 zA;{3fmSaV>RR=p37Rn8-jDw4%_>;!ufvGye}F6o#T2SOuX zv?$C+rWIO6Hlwvp)pyoubKFX{N98#Vn5Fj1-AH!g&@S#uzg>hmfEoqxtSeJfMLR*u znBy!nb53z^@gojS_bKR7-SxOXZ%(bHx-XP9MD-_11#J)q=ZI@5JwE5z&^^8#<}xGX zSbeR&gX3;`J$g1(HmUT{ZmJo|9BSh;PNmlSJ1|Rm+9lSLf{DivHB}v*A*fi^x(u%# z2)tJNi(V)ru;t|&;6s9XB1Teb+-@=)>0A z7BB8}QkswuVlcBMX4Y{Nck$h#n&r};?`+zr`Rbk7jGp^Ct+XSzZr;4};jR^PNVThx z6O*HIypnM$#?ZNBt@3~7)Fjz{BZ(VFHJWM5`GUnxueZ_72z`5r9FOICu>6w-#gN!} z74@rlwQc*X+VLVi$C#ht{BQBW9ss-ac-9}&Uh5XZ5c6INSLdQc(xo|VqGxT3IO=w!M?fEbYZmM6TpQRH@ta5a}3HWUo>2)&Gmt;eW*MufSyxht;C zW{nF`eB#UOwUwrn=ux(s(s@qmiqg!u()?gaD}MfNPva-RH3JS!jxTzjT@}GnO=ec0 zmhc#1jfSzQmT%g5nB||YJjTB-alNR?EodF7Fp_~KtCkz>thrw4XIA8<>hRljM3Q0Y zJ0#bU$qKM^+2c-8EFAM!{{=rYA> zj5A76ep9i?v@<1uO#+&vd2e>sl62`CfAvhl+(95d}muL9fycl@y-q(<^v7^l9o}C$~|H6xu;aNlDFMw)^#+ z!*SEi0#*YPw1b+LW226WxNkV(&#ayfOsby!=(+w`>w?47ZzXrb`M$iU?E#A%p?i1p z)w|;!k>{vgZlwzPM2J$!7<10$P+fh-L(Vw3U4`k%y~(!YJnYolDt4IpFKzL>w+Z4+ z5R=7$5SL_LW;+|3-IbdxyH7^==bBV-3laHO`nqS$+7o~&b?Xyq0qSylGbda5l~C)j z%$3=sv;Bd2n%>I@Qj;a#<4~D5>CZFtrJe^ckRst_rk7q7?L6X&b6)g<+8X^OdGXze zhoLINPrhUkUXC{@{?BQ4B zwr)O7T{qIuViDLqpGVnm%Z5_nAR8CjzLz3st|CNvuDYW zWD;lQ7PK(EG1pFCRKtX;k)Iwq+bty2fXTg(B~iuPm$fDPB=CxvpJ7&0O6?|IcyY+~ z2UXLq=)sm@jsuIR`hAhv$@KJe$=%GEf;&`&(k3U^Mk@UL{c&p@GTqbe&|W(`J2BoX z|J4R>OA{jpyv)li0$%%%8XY5!cBII%KOagMP*+YTSByHnFgg=Ik*Qs>u{j!!^?C&_ zW8)aljehep5q_*uJDMeJJ=>A;7?jzn>WcxlJG^VCXB3F70+)tePfN8wu|VB1Wq`~4 zX&R@Cku4pc@y2}rcywY^!f(9G6E2OOnR(L}PF0G#omGoLz1U)#QEQuc^@IS@?DxLh zNPF{od>7qRzCOq7y|)ZK-g&z1QC+zM0S{z$u_M`^Z65d$6|yDki}_|TXqeMsSYgowzdYgl)6tW zf+{W=j{Bk;E7?z1o9M@MzYirn3-%i^i*k|C& zXWI4xlzNzZjvWn*u2N@Rxbgeh0B&K9MZS*DPEu<6^&fJxCGF8tB@z-6&xcd}JDR*# zA|AM|hlBcj04o!?A z#@`;zHTdDSG4Bd%nP!me4(0C3^EvM4Ilu7>t|n*H71g%9UTE;V0y1?n_kyG=<}#al zLm*?2=*~2tDHXYRG;x5nj8vTWHJCFZMtDJ+XJt*d9xm6@Ju*H%PILbJr`D$43VL2s zMIdMdAUHN76$#!PQU*s9)vlb+)193q&(r-MkSWq|^o#rzj00W>_1a;va+7X9qq{>p z@2_{|;XEQwQPWQ~I9t0vXj3hu&qPO;^1N-Y05|l#CvG4`3>crva%_ROOM6es$**jm zqb_gE-0dy{28{;jWTL1xu+>XyC5R@%vy>W4@CyC2n9 z81T`OFkjC&(CI(X%=Glw6;(yW7sJj&_CR6SOWaml1YoI26=BxcFkh7aG1>2?fBtMy zdkx>up;g%S8_=7k3AU?TWhFUS53q1$L9h6#aZAO5!-eCdoz22~S617iSEndl^l;C? z%7ZvibUBRiq2%-LI-&jq>J(W@G@~!=AZz~K0&X#SwR^*Cs5EbY746Nd#u*w@QBeUH zRi-6Uc(KCa*XL`%aEo@=w)N@?-}w;>zu@z(O576=O?$d39lHjQxF>~eh8;~ddWyz} zts(a9rP45yn*M5DhPECH`LolR;Rlh~oq{){0@r4{dQugbF^2bJaE^mK1qB6JiCMk< zJQ)RgBzFFf{LG+HxUPEPCTX;d-%6{xxReI&Zv56NGN-k&!Wy8*0|fNE_t)`MF!M!z zO<=ZUtqLKCe}0w`v>}n>`{{jSj5;#fIP-3{BF+m^1Gk>g5)%A5l^d6|T$caVfp}xS zbef*`7pKCxbLRjvR!>D)>})RD+;iDDr020L ziK}v1{$4Iq04B%({snL5Re&3)RE{~K zj;ML`OowFhui191A}cRgcXG3T4*tA7ll?sJv6Khqfs%H6xky&;H#*$0?Z5u_3U6Tr z%!ShpQn-7tY8kO!JB{?=Jzm>;+p9rh2QF44rFP?M_T6QG6r2}^G^seU;-{$?!JFEs z@=fP-{OmoG9DcCB=$-LUHiGNXxtOU!v(6`n(-Uf`y;RlsKG-!RObID)h zjf0w09zT9V{}^^_Oi{$Zc~ByXo%C%j@KJmgT}M00(twhT&ibWw&w6$zne}E5-w1k; zK+z?gckE=>sZozrx!sg0_ss>*<>c`CK*o$?YTypiQee$`Gc^l9O}xXRHpcUMVi)ZN z+s?e5HMO(Y?1s~mED*y6GQg~1+b6L@m^;!fg4VD!Ey74SFqRBNcTYSz^-ewr=UveKJwpVpU1Glcd6HT$xTiQ)JPr z&SBA?pUihh;sj|fr|B3OF=1gTG`f1;9dugziyrr65p1;h^p=@7`?PSkH~f6~UZIwQG*^>X%Fy&9frew4i*Z$q%qEs#LZ|1J+HC zPg4<3`Bh2u(|rr+0fh$7&>y#%{4S`OiEdBOsPPe+d3bn82%rFu%JBDH{`9@jZoD4; z$cINCkhpC7>3v>^BN-YOoBztdd_G28^ za;S!$5=J?A2DFE+m#66NDt@6Cq&{(H=6|&S+qe@Cm`hZsSZF)Pe)wkGH>09YTiMxN ztY*ilYBwW3HkrabWmX=~tJynW>v}7Tr@*{pRor-En$|(FXpI)?G~b2(YzPXY+X~3Q z+5%hrF{{c^70dMu3c|#~QLo(=`u7Q4Y{uXCt+bv$?*kS0t4Mx9fm5Hx-e&CKUTT+u z#==x+H%n!)Z*Oe$57RBZhh(Z8*EY8LX}bWejyLWGi2dwg{~;&hsZ?OJG8WEcB%)!d z@x|a3Pw}Dv?O7=Hp4r36G{PX%aIVvl-@kt!CGfX9Lz9EK`y+tj3yeNP4SbhcTU4?J z*+T}5wD|7v^Q%*Fp!&(uuClN7mpf%FQyzvf%^k#xc8JXZuQ0Gs6jPz2xh<=Rn**ZD z**W~dFYxk3Ua&lOdeDm_#BmY3t~ABGws7TT_J?XoYcL{chm1QEZ@}7TQwH%eGc)_B zsC8V% zY;Fl>OK~nd&&#;o+$(z0akVMD_UKjHJ#9DY&=IseZR0$fyzdkTlPIUYCC@V!G+XJh z@ypbW4FV#dkgM-PU7Y)*DRZtnqU1MY@Me-R5x1A@LeBb6Y+HqSvt?&rP7|xpV)Gw z=GyexS*sFopumgD7|KQ?KIVpCTlEBU4_khiqCw(@GCK@KP*Ul|Y+r`A@otaH$b8KI zw3R;%`%R7fkwV*3LLx%xEtI^o<~rDf%{=N$Btz}r7xqkeJ^I}?r(A;$&H(W_X-|C7 z|LHH)|Lf(|qbXdivka{N_3|dCrs)4KFHhSLH{|iZR-TcG3B&*Osf;16BJ;ujew>_| z`hS0Hv1q07Id-PJs(;|7RRma*Qs84ieXBPrd^W&Ej-;XgGyKpuHG_j=3{MX89*7%^ zy_P!yCMJOSdLy0EX9(gA?kgfaHPV;ORejYx)_(yT7Z9Jph^X4YUxOA~&sTWg(G`a5 z6UlvybT<@kY;9_hbC9%8ZE33qN69HXENtp);IVL1^Z&<3wl#Y7Dk&w!KHxlZMVa)FFV$Z6;rE>s1a&S^P2gQN^OqF($dnjw6qT&KD4zx9M(*D zXv88QAS^5_B7%8OgItLs{pkek6H61N4`I#Evmp(kZ212EI^+3;Wg1hq__24}OA&`eg`tlx)7}XI)pRy35sau*0jg7{I)B3pbVj&z z1N63SXg@%CERdQy_$yuD9pyyA7J?5_SaU%CnOo|saCKnxWe~8wa`|$4M{raW2u13l z{EYtq0B~-(PZpQB{O9{ye|#*sWYqYS+CWea=s2_l2^G5wIMT-U29_}#FHT8_2BlB+ zR_63qc*4cb)Swt0+wT-8md)mS)pxZDr(a}n+_}?NU=oCWBRvM7>PX=sS*Z>w{}mvX zW)?5S$95V*5b*_@ZmJA5TjICEVy3638LUeUppD|044?;%F**+!u*Jc!88!q0aFuFg zY*EkA+n-Jh^4c7BPWk%jiqJ~&pfx+{5Ll=C_GGx6W}(&KhS=e@0$765MC}rQTQJ62 z>B+3&u;ph1E)0fNMC8}zRPD^h`!cZ=mr=wur9*3?V_?X5`jo|`Vv6v}gtwZoz7z)!klZ!-KvInkgyQ0NwkpQ&E;yk=sVUNkb4b^TmrMZ{)Lj=5ne>RG-Vr zezNB^!c@9%ZWt->n|D3ss0RyIg?ThY5`?I=$CDqEjnU`n=+Gb+dxAnD?n})FuI}Nc z_n`)F0XsA1jq)pg*Odun9$}EM>iq9jZFQ+A)HO6P916AnING~3_9Ni%isxFpUZn2T zw5>oUksWXb%D1P!l~t14J|!g>YczoPzyx7<&6L3!{dAaiVhzL_9mjUb_;{nV=B7{* zTljN0=s8qsN9(OpL`yRHz_O|-m+f{2wZnTZtAtv3o{KqLBY(SzJ?Hdg?w=v=s?H@R z99Z>|J*;DNo%nKq;swo5Wc4?XQNVAYSBD_x0dwx$0z>=pc6p4NB0ywZNQTMFJoxSh zG!V6FBR?%KNlD-VCn4WXlf@uo*}3xh;)OEO_23G z>Nx@1Ge@V)9$<4>SsB=3;H3*Kl#T}qO#pNOW<1wi~s>2Soy^1=dZ^*{buAS|cW(trC|W0VSaf6s(O`+vd{S zrn@qw>Cua`y*Z0A`gP|?A&S23oj}!iKVra0g5)X5Y{-uo)Kwor7Nnp-JY?;9Qoi-q zA2y45&NIvkf5{V67Zax9y77%8xT*AbEGg#@ETw6LFsce2a%W z`$)qQRim;y7N%{T#{$|MGfX6qfx(z@LpMSDiC>9y%>lOj?pB(hzQCgXKD2T7*!e-_J{)%DF+2J+I9cRFgPk);v zLIA8^5;JWX5SF7|lD*GjDsPpYFLVSY>+svEf@=E;;w6 z!93`xS|4!d_w*!@x2P0YNE@<6;qt;nUKpY*xo`a8I`$u7hzPJD%$g*hjY1oXcF+S4 z42W58WWP{LB##LwHhSY%XP#!uaZe^|aaj)ux+|MVslJ4yrd;lwa~WGAvOj+O@Qv{- zJ`oWeeZ{jKDnZ8xlc>j5jEH-*cee&zA^s!6c8&x~>Q0?Fq12R^DMcv6tcLF1Ia5xE zafiIBT%D;1eolW)!J+QStrBg1n~?{wbD%&BI$Yo&g>-J$1d43*kv3ioxS-6SQF*GM zp}f06P*$YOc~Ot=fqIsf+DZmDH#cx{VTUPM!f$9B;Y(k^_(bhKmIqc^bV8;wW*Qc; z7f3(R4ozN7L#-G3WlG(lf%fLp6kU!pzv~vdthJ-4B5V2)jtmM z`CG~}{0LTXz;POmEAfzI+(oo z>)*@ILms`>2HKCy_=TlO_v}yPK0l1>jmd;ErlX^iCxg8;WBK<008Q26Ac&K!;e{;g z-zyi)jw9cb9L>Pe*_Z&`iQQhRAS1iHNry{tG7s2R-X(+I?M}uA%<>~xp2eCW1}STsA8UOTT}@0XEMfp!ER%>YAzcG6Cqo6 z^XARz&(;dFTiXB+cMeu+Yai!yQyn{<$2_)3ncuUp%Z~L{A)`a98Vr@?)k>`x!&Ca zty>?>Mf>;3HWx>2j@)2ln}zs8(dZo(7DAx#!qTKY_N``tu^fo)#70Pq6FhgMLn9(g z#Weifa_`@V*j-<>x3`Mk0NZZaM%Jpu5uX>3Unq!93}81ZI9AOW%Ov6^c+Y|a^4@rO zfRr@}feUK|?2_y7{?+5R6=FR4jlwmD_X`E!fz<=A*#Sb2b{5@-TJO17`1~AO6TT5v z&}9hI(pEj?=e>TH`x+`m-a}PcRzgP=;=xZ{OxK&|C77j|<(L(jRhZS8HJNpoM-k)* ztC-j!#3V^5gZxW>Beb)j;jsp0@3s9R^KHSLG2LRz$P=H~vrB7T9ZH~PWp|C;kWT5t z3(d?sC}APX0?1!bL#=9eE;JMD1-BdS`EOz`B299>iO`4pW4fLNQZl*Js6GU68XXKt zPM$|e`A*>M!x0LtV3ZFuRAO@Ag$ZY2^t^4Lt``8lK|{~2g~CpGhrqu(c-&%C+;mrZ zutP^i(vh?<>?_OEpKMrTS;wrtrM?g5k)5Hk|2~Vp@arXN8tyC5OEpVvk&)k{Q>&_ynjzuX;I1~SC{6xIupVwju-q9H9u6gT79^`c!K73h}#$u zpa$#=LxlzSb@EA*50Z?)8}K*n0E+SYA*v@C*W@9`2=S2Ww@4Xq2w)e#RlkBm1#V$B{M?hY z^7CHxUdLv5hBCw%zsESf_aSqFrW6awf{lsmXQ1O}NayLoT2p=OXnJ$M`QXttV^f#s z8wZ)S6)U4q!xZ1~ns-gZ)E2PNa&YKX^lc47e0SP1tOD%JgW1l_zVV>gw(4rnYuK0h z5Q>3{z7G=fuAi>x&6^X_X9!N;gC4AW7`18ag8h`$Q{|v&U@*o}GNk-Hj138S8~E9I z$j(LTn=}m@^n%*kA?p&UR*($(7nXpUnWD2$wYvb& z!hHpRbmq^&_p?jcnQ~Gt7Wtrhp5*nczEx{0rGeYu-J~SU2eRd}g?L)jLfA(KO!L>J zDo2r$bnc6d?lWe5{B5IKKO#9N^fm7MIekqmsUxY z=R2T*1TkcDNofzXajNrW=gna-QTgC3h`_L;3b<$Ujezc|WfM0qGz*&w&BoHuzT zaCd}n1=|^u#kmQ?9s@(sLLpnT;K^Z#fLF4Q<=)mZ+DA=M(H06WNuE2@EHV!rcnTyj zX*g8;(b({K{jh7VE>6VW-u}W^dbjy##ZW%vAhoT_NI|F+9Y~QnKt69U*gSETgC1CD zs`KL0h9wU_R%VZ-N8G|l=wqRQev?=$h1m~;a2i_tmr|qF7!fzs9S2CJZ51D(0axCZ3W{9IZYCLxdUSn8qjc>epu&uC8!o;Wtm~Cr8Jo z>=!50PixJ`Jm5O20{niq7vMnrY~rc{JSYyp3Q6Q3hf@#~FL9rj+J5~sC1*C3GD?1B zTXYvu)qtR?T_9W99XXCWw{+@rpse=*vHG{s9sk)<>^$EWKOui^qVA%8W~))lX)3C$ zaV5uXz$E4}Y<)TLunGnYDR(XO^hQ2i<^jtK!yPbt3h}x?L&HPDsoWeKL|_cu2Y+Y> zXO4c9H`MXkp2SC@Iym*KJRm~Z-``IKF7HT%D*=zG>@gg+TZH%+PgVOzW)rDu5?{F` zz}(h85=eyODaSBgI}@9u_qK1LvYbZB2m`_3Cp^cYv(kO~`ufgezh1;B?|}cQ=Q>G? z`Ob|{Usk(X>@WR6^r){tM=ZJL!g=bO&TuM;2oPxn%2hB0 zNZ|l>CM@gM%QQJ7XjGeK2~A6Lvx=Y#Sb(!czZiN=2nbXHYX&_iH6;Z_ID`mlPvcFM z1Ykra8s7o^-nqkT{rC4UDRuGloK#3f#VVc4V2KSW!Y`Nm^XJdga25ct{s9=#Ym2WT zu6+sdJ*Kf)d@A7hsh|MfJ2!}h6`2)phCD#urZex!B&im6$_fZ~bxPZWe#KQsLn5Pr z(K%WqU6x3Q_2wtD1EIrI?hGgvIusR?t&P>texYBe#n# z=_klIDq^QZ^k3xpb*fp4iQjHEQm9*|(n1Y7aeZFc`9|US3tq^g5I{8I0E%ltyZ<+DvM1BNqD4#=`I}No@m6PvtX< z@6OYe&~a*>zaYVc-uPmUWO~?qIdPL%K41BovEw*$_R7xYj?BHvE|3@8@gS*$x^6pv zIzE1a(gsK5cee)sO`@eIe82&3w7C12Zz4oS9V;N6-o2Zsx++p(j1@}qP|Ui@Ck?z_w-0^vDV#FPYu*H_6=LWc6QH530t;XW zZ?A{QL<9O6YH}i&*=;)ht&u-T(ZgtAIV`>Jul7fTp$sbO1fW987quT#qbf zQ<+UGL!HBu;uxI48u5P>YWq$yR7~x`U%$lNAVE!w0PO<0Yasy|mm1ZlhmOxnC&RSCxR`GQ6{LCd^$|~J z(Dq!7(B|A203Uut5Dw}cR&k^$VO8rso+^;k8l*y&rWOWzlb&5Ysn z*XavvrQ~EtC?_}f3ech+A+$S)Kkl4WTYteKzU(bL6=4Wl<&^@P^fwU0mj&wH;Hf=V zdbDlK$iG-jLM)bNgQ z*d=)HFZQ&0yG;yco$7h_{(YfXwR=mClWfscIOB07?Jg?s5DrnS&vZ7#B+`X*`M-UL`vg6&M*Cy9L~v9}OfRL-omd@}{ql)t?xD zfnJe(yO+%adaL8Jw|J*JuV+7^QZe1g9*A5a;P)An{rFjG6K~LOSTXfpy?nbmC9V!y2aq|wxZcC~xwJl{x$pP#8y{c8rxoE?{EUh*-k3g)>* zOl}fS(=>UU%&eaTvR)+C@7Y&fKoreD`vyo&+W-9yU`)aW&OiUYB;y05n}meVftpeh z?0YWb2ZK?nDapw*ik7V;EX#T$Cm6M1*U?k$eF&9hFc>zI7-UleY%1IW0xpSq zxE~(;tLBi0gwtNQPyvD~I0{9#gG4r-6Q5UNDt%UOq`9cM5gz3@xNN+TMzFU^>d|9l zSLCk=tk$%-EAb^@c!CFdh>ixWpFdast^GMw6h$RyIj6vIu(!m#Ut>}HRMb+UB<&ya z91h-h%TzSM!n~#)vKdA;d!^1qu>-!*G#h{Qw}Dj6B5(vbG69ZL&}vmL)C`gwqoseF3q*dW z>|Zge5^4*NF1fjaeYRA43T>%T?d3^`1-_x!lZ~d!5x(!5)pM`V0EoHmh*~a`GD+V! z3Ln3MhGRu*n_Na0>FB1M)=2spCJ$4~ia8)zfAd0mELt98r%wj3yjs($nHw+Mw9S;>CvQfe1!)|3KLgS{{n#5*fC=Z?X}+AQASjGtOJO( zNBRai44U9)NxQjQ$%p_mhB)ebJ=5vh9CvNQaxYq34^$8^l<;z5(OSC^$GZZeKM@xW>e zJsGH9@+$R}={va~n@$7OGdThMRkpz|aVTGbH9w*`^T@BAjC3ELg(t|vt2!V(lhPY3 zpo8X=Z4B72NjqFKxp-#$W4&ydOJ%O9{+aFG>I0+E1b2WBBpJo15R4nbD%7+j%5`JDpj9gsBQLV9YI4GM?hCZ#`_Al&XqIX1$<)Le$$ZDgi}z&MmXh2vi|xt5~Nqlvq+9xlfafPJalw00HM3(zrnJzIT!?i+qwxq7nS&O zlmfwq!3jrU7Ru9?6nh2-sjdjN329or6kcr=?&(ffX;@gDuIL_yxUoj&a|5<-8kF@d z-vk(t?r<6R!EK8Ne(yb_SV|5vT5U~TOhIdk%CcYwyUljIhq*^{^~d!ee;IldAD0*{ zh%J0dEVWw1Vj3CgZ}LhHRbH2I$XDj>@hba0$8mDypl?h(M`qMNoz<4A1>yuwUfBrM?3P&E%1ebI54sv1p9>8?*gKPk% z;25C^K<|{hYiv395BwOlHX$2XBtR1DNTKjfj_3;OA0~ddP+|Co1GvNOng4N0%D*Sg zardrH@jSC!Xo5!W0UQN788feBK|@0W{QTAEVpk8yvV>vsr0I{z;KPRPj(5S?4k4lb z7}DYACr{psltIj%<|*kATdem%e~KJ~#R?#^T`&lQ-TInU7XT!{dG=Z%O-!u{BB{3e zVky7FA@( z6?#AnZeYI-1WV(rtM@lZn%2<~h>H4BF&H-`>sNchF^CEgA!zHQ!uHQ(_e)3WdS^i5 z_*k)OS&invOZRL0(^RIO&rg?|hf_liHs%V%>K98~N=E8jeO)BI05={(j{Ffiqrw@? zqEIY#wyCjkdGduQ955Fy4knr0Al>Pi==jkxF?oR&DoEG^?kL&%;r9mDWI_PxNalRw z;T}N@ZtvTP1n_MJnKp~z$S(x^9MMH8*VC`Isuq*1D)t9dL&G*?rLQ5fRQu*}_1XpP z&6bnGtQq|&@`<7xE8Gsw_x2lHQ@7spWwH?QGg|Y~e`i?)V|q;;dd8Lmf*<=^>-{`YMI|ILjKI_0piT74 zwE`&^<}48RU|XuV0lN8v&Ke%JifyYkXWBt*+K8 z0H#&7{JMBHznRkk&no19)Q9aAh&ceV@)ybDXs*#Pd2RWz=lpJJVjEM#Dxyk~ek3}i z$dt!yw|Awh*p$!Q-VICF(6Orn9dy2$bRMwjiCpq*PZlUi!&A`(a8AUYkc425mTVNO zj7$XeSRA!*HT2&5)G;xOe(@A~rrHY1lcfASk>^GS2C^a>Q$?Y}nu3x(*T@{k9 zsd7Rtn3}m$;4Zi2XnlwXJNlL7C}_3c!b1n`6;q=)4xFdNO!QNjh{At`XoU;lU&(3s zD;E%GZl=rft*;y{A^Wo@mjA-U)VGt@AuG_Ua$y}Dc%cUSN@($(rz1S<=JOiZ=n7e? z!DyD=+uVo1??q0XG<9DQDKjk(k3I4;4;1b7gWy0eSa_r$!Pwa0ZmNHj!vE9WcLzna zcUv}ky($J&k_n7Rk{}97MiG%5n<$_lS%Q)zBL2rQz@3q%jyRphkd4A@H_Q}hj^>#QgYy@vu zfhI=0z$QOfWGD^r@Ucf-NVIkL#Dhq;2hcvs(5?V?oz4`0ogC_MM^4jz#zCn$z>0^W z)@AL{4Z*?`aQZ`S3YfHwQJvx=Bb&JbQ){MC--xPh0RVFdD-9OU#m@)r9*>jMt=nFsr~T&puUB;1Z-^+_ zCjg=U009>r_7{jVdl6+GXIX23GGhQUAFqhf(#u^$+@?DiyAR=D>hw9|&r8)8)iXL! z6+*RH>48M$H8rR@seu@ytyaRD#%xqU*eMo3aTp&u$ zzy=`XYIO||zd!U9Mb8aBRt?$t28Lcb;Y%o0HtpEK(!y0@d!l90Mq6c}Cps!3G@+ii z!tBDCt1ltU#BJAyVu(mLg98I6 zqrHK&L=|W_WOj7qAW)lj5G?Zbco3h6lCz*${XHnU8(8n307l3=(LwA3Jo=OO!otF0 zV$PtRdO#_fLGIMr+Rwr`kb?ep08uI+~BVcfSTWUZAbK`;`A}vyOMg zryDyQ*9f-V@o&ZQ{l3!OC3luaWUvj)uUKD!{sQDRfLqezeS)`32@VXf+AONyroWvO zvba!5Ib30VRieSfYALrk&$aQ&q4S~!9fw6YDREJ6_YdC83?B5j_~2&8lkJ-GLOaXc zm0x_|85v?^+D7^08+Y!`lH%5N4R_0jCznQVxD@uv@L9T@=++w@-gTk7gzx>^v<^Cyr_fU&OS88PGvy14p&yXRC`FQM&|N#Rbrg zTN0)%Q^N=t0k)4uOFjnDt{@!;cV@{O6sT8mzu%7K0TKdLjbxObLdywKlgFFlrA>3JAR z)Wyva4BhdvEpbb%w@Z*dbuWJFx+oaEpXFCey(A(ZYn~F5=9D?{Xf~nTw)yERoZcgA z0)o<-i?kZ6>c#6yUHdN%r)J-@yQedpS0QdIw#P=n>T6tJG*1}g(!yACu1%B3XK$8? zokinJsU;zqE+5}@u{`;eu(HHC-Z=?-`g6S^i_O~@O;s8}OfP#8%BJu)9cz~2DDG2G zaPaiPbwI6`K?G>?9lz*W%-sC{O0e+MJmeW;xwwb)o5RI;u*>ai9A1Aqx$yl+asJ+&#nZV!&K}& zX3`(bra!{W^<;i(R@lamWm|jvsUF_dye>Vn^Ay1ciytnWeYDcsdGZnm0<*Ek=M-s? zcHx4AanS`nce}6SA>Q({4w397+@~5Bv%)2vDOUO}y*I?89<^Xvuig%~rUq0c7qcZn zFzMS40W_Qdnk`dfiR2U_k9WenevzQXc+QhF*TE5N**kEwl-M3%c%irr&UTfUyo7Xt zaMr<0WOeR1w_0C7P>`Hm3O#q_2smU7HSB@R%O}u5X9f z*k1jTvpA{an5{$a=+=Ah%Q2bDY>wo00kk2?{yeTxPwob;50Lk7XsWN9UeBjr5X?NO z_+51=B*d#{y3bAHT$h++pbW|*5#f&U`zA0`f+XR3r74M(zjCCYE^9zU(i=p<9i{K= zlE5TFQ6L@VbQGR6HN*8dfpxv>+r?U~5`Dz-5~2@+(O{gS-_&+}*o5FV!|Ru6y~EA-T$K zu0&nL%ld`9!Pi_ihjB<7W=w0Yof5QmN{bAOt2DW@E9Z6_Q)nj*31F4EBmvh41*C5i zBjW$dJH%nM^#G)y->{#++I=4gTiE-_n2)cWpJ^AGj+WgJ+&*o|YJ5V-v$Tuv;(g~> z%$4yN<{Aio>IgD$n#dUzf6k}1b#K-#x$kZt%2=4~$$n;4T5nY9`h7cYoTX#&N)p%^ zI_(ZMs97Gh8JD)RB|dq)Z@TJ&@O+{4ix(&My-=Jg{yLF-y?l1CdnR?>S=gqA_R@H9 z3$E_=z=+jA+@bRX3;S0Z?HS+bN=a@JP9cJYq0!AeH@Z<2Tnyp)a-)N!kEDUy>DX~_ zm9VBtzP7!cyPo@v9Gn06EoaT+hF54vPd9hNvB3ozG+1Aeq$1P>|Mqy(R8yy16Q~%N zQ|zkD1qt=6?94lcLWM=jiJ4ie`vJi#iH0)0Jh!0mvZD31=6c;z!KCV3#ZoLCJ z9=O!P8FR*WP2j@+1T425?<0}57|TvmQgq~N3=B^NU%Bdh(|KLgGQV8PN!`?~yYtet z`G9G^%uAUYGUUpjncUwak8$g%1rp)}K10d!2aISl3TDi1oVs>hR`!~V-!7}8kGT(W z)Hl`E)zz})EjwYRL;D=aT($S$qe;LcW*htP(drXBxJrk(x$Az_pqi5LIDPaP zc&HvAr0Nxbbqa!X>T_Sz%A5a^0)q%%7r`J8yCDPjn)Sii0aw<6bx+APvRff%*+X#h zP;9XS%K^AfO2)SDW1z`ljJIp*H2=s)bfj(IM0Rtln3Cpt_N_SyIqGzaiexez_#P0{ z$gWvb49PVCvtz4A=Gkd??vixkpPli$M(cm;RC3B80z?2Y+m)es-r7 zTopm*uC)%nRFQc2>OOY%*P2T5>2F3ZkZ0WgqQ1v(`%JZS|E4Wl-l3`liT^=PPD|V` zf~brDKY+J|*rHiPU?Sy)I|cCY7oa`(P{|ukpva&7asaob_<$(ZZfxAB4DVrMO9m_t zAh=nvvK_Zse^QGXG-W-ao`+ol3OF*+x$m2{RDyB`@I9Mj`Eyo2x%y*luFpu~>N9eQ70I6VMUjuDlCDREX}pcnObl_2g$=n=xLopXe- zdr#M=E-#*P;QlsG_#}np*lL0AQDBr|qZ1KG?LN`%S%qN4hBg#1A7 zBMLhlC3ozI!dI(fXg zij!acLo>7?t7v3;6TfWG$PhGsxyI(k7LM!8(*xqTgOZ3{wg~Lzu27FNeeF#9kTAKD zF{t$kcd?)uO~Xd=6TFDSt6h*!#vmi{pJkBXvR4bd3|kd~eht$N&QD}Ya96oQs-Ypu zpbh!5)i(nJe?M88mz43N$WpXmS4RLHbH@z1({&5KvPGUrn8k1873^NoRe6(T|GlyiNNiGsPl;#Oia4YPYzCi92iYIvZ+5CAjoy+;u52^ z;Z*_)q{+I2vs`woY*5H83>D=``B4gR&}|$ZIowl+O&>nh>@ADac|yOiX6hG|1mK*j zG`>2r1X2`*ewcoc1%;+b)CTPUP4)k3O}@1Faq2#sHO$JK9Jl$dT!LFZ#EIOMl!jZR9)ZW8sl!?jt^?joHWYx`ps84 z89EVQ|NSpdQ}{`RFTQ9Fu>nL$?BXP!j~NpA24JNyuJ1v?5(6+tBQ?)`3Rxx|lV-Zyx)=r#66|Ac~n{U#P`HcM)iDn?_ ziy(MzRSC~Jrg(VDseA1uoM;h5|3Uh}tJW!12NoXs(e29Vz3+8Y} zgMO2JzN?HpW|O_d{F{MZ<+4rDLdc$I-^}k^LwXP_|t_y@c=ePo62Bi zambX05Az*jxc0mA1?SuXAaK*MjD1Jj&nds_gU3XJaW0qmJ#0kWi>kC{MjdGf6=&N~ zTFbUXm1~-?=9^9cEB{1OC zKV-NRRoj)!R!J8rI&&A-sZ-JyvUaP_zn!g`<0}4pp}I)Yd$yl>)Ua=GlNF!+bfnGG6D>1(9#ycQmaNg{m* zzpQA>hF;yHveu{t3-BfLQxw@)04kQ`3iC{p_0D2HX^hGgfA)ayVyeVV?Gg)5j#nIw zvt2&KL?;)8Y1R3zS_dKfsNCqbO!x3&_l47Ul<*v;&P^Fyw()7#VKVL7V<_nZpXL6D zl$WEWGa(KsCGz(Zi~`1@ytL;QtgS0^>Pl?GQYCd`cQJk%EMJ@$t5~7kNx@DZ;j0_Q z_HcRVVB#dk7dziEDIf=w+%=fRpQm*e4UmxE(cOMjyW=HxV00cI~j1 zkD=(Z;a?v+wS%UN^2bdFjJy7(_t~|Bm^Gy23<>5Mrb_)Mf+E*?Zq?U{LZo$%z$lpu z=Uxxy8kWv+^@^T?zf@E9o&(`8T}|n$>BF3c-mi#6in5FawARE;dcFwnBJ}+zAbDbE zzRPPchrxVb;N3LgEe2Si8moTJ?{9FgQ1wkZoA~YYcEha91Nw*^;9GO=-aUlly;lPh z+dt^-as_E{2f@&b3Cj&fm2oSGSN`ge@1oPCd((%NB<1cKIl1wF77B=lCji=1WlT z7o%qOjfWha&vN8Y|CUAc-MczKb7%bRkN;Jc}wf3rgR_mP=C~>Gk&TU%nj&g z%Kg?pe*gYV@_OVf0sj8uw6>2E{RC_};7=FPY1sglv(e+r`GQ(fx;8&AgXL@0mf-|n z7Z$L>z>Jy(mm!kZKyPFm@OQ=YXfVS83Yj}gmLc$;e(%sE*q<2k9-t9OkTZZ{4?2B9 z@RDU%*<~HfT6xOsXN5`p*d*%L=d$1Gxi;I_E6-I)hd(1~vK6Dz*(lz8{pk5}vQAB& zjHgYoG{4XUE0``6`7fNL2a>VY4L-)`u#RE?nGRORVk3cToZM2qXIq~xxFgxhsPo@N@pcf-TK#Au|js5{PQ zKe#qE!9lqJg5XTVppf$`c?E^SYWttt)uqFt(acJWYRr!Jsq-QKM$DtpBMyD%eL6Os(qRizGpt(};m8-!EZgj)c>;5bNO z4Uftl>?Evy8n3cVI#YA5-S;*obSOW&<{O8^ir~afD+)H}mVKs8J_~x4(4tw87(asK zVfB-Bn7;tUG=ev1+;v2JpE}yR1U1?Z6hW{Fs^@&#xxSSdGhp_I_Yz@BCTf1*z=7y3 z8vBQ7kDD*1#eV_In!mr;_?3Tsz2YkFMut=OFA^hb;xVuG2-Ip&DvrqYpG0h=ZfpB} zyuU76m?9H??%cUX(ZAs$_Q5}@_M}l%!IJ>qiZ`se(nc@g$rgL%}!h&&64VxSIR+ir$wukbZWyZ zLtR~6t?|U2Kau*1c4QJRcsxRvzMLkKLkgr?T<2Taxm+$qhK7zK1u%q9I=(Mf;s!09 zt0kure-QLG`Qq=7CzRPVAdP@L`4gbcoMNRovCi=8Ck(IOp!J@*m9?3_D1FXyaG3I7 z9?<8@+}J z#dWX6w%wa$?%kAg*F$LIIs-Ue+e1`)`%s)YPkqd5?n!f(bR$6q?bNBZhP~9Xdl~}3 z)|jG&`L@RoBs?qH`PVEaZsJ0ATYh8wSl?iC%7&j67buY%hQB6A)v}U>P`(8`1mDzQ zzOs+LNHKF;6VCDo3k&P%td0DbLUs#kqFj5T_hW2rbi9q;O+{^G18J==^>bUHH05%f! zFR2!}*ZoR<5;8OdT)HNr$ufwKea8!sx>(s%x2#l<>ja-ZCIHbWDn-Z}k#1w>Xg4Yj zV>~fvJyp?dphkG$dI=kdF=$wc&rPQ)Mya1jePe7HV%a@;i(%W$aC_6`=?Ul;!drwj zc(Mr@TAOi~3FT)MtqMWv>c7{;Zo1H#{4V$VQ@X`)jc|&t4xza+0 z`_z!B=Vn?PLWM-gWQV(k2*3|SJKEP%((k$^d#_8xdo@*=cQ$%Xp&n$(&QVb0e8Sc? zXUiwVu=U&6xSQI9c8;97O?_^^6mB}1&-oYHx<3dFHO{z+aZy}T=eEtD9Ay3DlY~V$n{gG*C z6vH}6MejtkWo?0UR!F&d6RYT|2{pJcLjGMh_B=5wpjcsHvsFG+n_IlLE-q~QwmSF6 z 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 z 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

Gm+tnTv60-cB{FuoM`$NL>j@31 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

a`+D%w1{X-8qbh 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
STWpNz5ezC@gU>$l0 zxe^zx!>(42_()`F%cYXKm{HZH*a>L7kGXe-|hyL_psS3X20 z_>5H5NGEg{(;NEEw7oedwL9p zYC)+rTC#|P@z!R>kF&S5F&C~vp5T4ien8YY>-X^8OTi^9gGCH}X*wx4F-@cN@PQrH z4Rr;F_=*>%R>IoG4+JJoB>^rMSHu$>AHXwLG}=Xw2Q`OsaZ7-6?j-k9Ylhmlf|SGJ z022wrW2BLn=;2$TU79UGxLMU)4iiDb(M-&jhcI%r0)1J{FMmg@V@$_wB@98iz&uh^ z;}m+F=(Fbl`~`Yi8S!NlC>Gr%$%T;wfEAE$>4TFCz(>8Gute*K;x(##n{_S=pcZh3 z0pKUuLXnIodUcBI3}kMhRxYGqp?yi>84W;*#ba20^x(lnSW~TAx2|as`x0h>&QKp< zWaM@8^hDX2*%Wh}&JFHWO5j7jp1~j<@#qU;pa~YBfT*D5kP&c33Y{&YdB|DGJ~*3a zv_YO*H~$P@oH?1NxuHD_u}YWp$gt0KN$HMQ)`xn&D)q}q2xX?YZ952E8t5!t%K`;Y z@wMo3YD3!XJS!wS5s2_i9J@N9ZShLrs;ZlQFaFv=aY~iv3*MUG#*f~*Hh0N(6 zV9sG>W>=7lsZ^$ZZJw$X{I$8I&4M|gufhO=fD5 z%S(3{2vwhQ+{>4fKq&zS31xvs!3^>c+6GZ6uj98;&;$qTwTrrJ2eg*JqB!4)^gFb5 zp8p?Rc*VxVSIeh`zVpzpwV&dIT++WRq_w8H`g^#Rn?JGq_J1TD#|}T zpqk?B4S8b=_caNwC68x>#zRqVZmQa^f930SYXftT9yVakoYlX8{yBs1+_{s7IoA&m z95J2dg&gZmy&twxygqeihiXxy1<%mjddC?3_vZ%;OiZ?iWHTEd9xB*UIpDtf@K^`z zP4fxq-=wI$z=1cGCv^H0g%7!zZLw7PyN?*5=ZEN3mgJHPWD-zpZEYoPimJ5`LdGX3i$RbAhTFLd0brQh0cu9HgiZ)l zjHeaq=wXhHi$IeyDqb%2)W>Kk;d%9=wgPa8tgIC!cYY7zfQRjsxC-EjrkKzJ$ThK| z7sKY`UjudTb*^Up^&CM>`@HYdb+MZ#HTg->O^X_K=C_K21vihbU%#Gim!&c2S!bu2 zXOT3ar}32A2mgLhGu6}XWzaq}yoajlMj`rehsPc^-3&C%ZeD%B(XEUzj@`~$In0cC zm!wDj*dp>fl1(!^oaK>FfC};ij)V0gbuVH(M$@qHb=o#awD&s?{lp2?}2 z)m6s;hFOcVlu0 zk_EELwL-8(=WAHs6w zHnpkDB#b}NONiKsLJIhqpd-{nARY}P;3HSyaVrUp6xPUqAUWGuH0OBq`ZGmBT)|g> zlSC^WC3MCz4|3yL6bZ+0cX9%B>)RN+vGl#puiEMt`j}Gn;Xcm&Z*uA131m)TKAC8zPqu#oYg#eUo_)h-V*p9WK^MQMYSH=dlI%J(@*?j zK0X0BVD%CB0g)b|k7_KBcFU=&`bZ!RiaH0}a6UPd=DJ+&^L;LRPn&lO!%5h(`KM_& zIe$RFChmjqmiZso1IH8-oETxzFAnwD@NQy#A^&_UO=t#*kT&V0UA$IWtvf`@7HZ^w zrGfgG>synk*F-*^RlkJ$qzO#3Ft=D1n-$=hZTAA z@jSdZQkOOO$W7lewX+;B-ESLCQ;-vQSU-tcx?J_AYNTe3B|?Hop)%XRnw zWG3CNF~kUmbN0VJ7=ozKLr3j?`mj z4ps8G{Zf!P{y^8q=<D_OuF1!AHu7>l<3FoiUR?p!i!Djs&YW%-EE_AS)ZT>8b2 z{Z9Pu6y%c7lPKX&iyTgMl_Nt;8}A3iF;bNEo%~dY5SzD-S)fos^uth)KUiUD2ddjJDx;W(gTnmJ*X%r8L81p;
Z=tt# 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 U6+F2C$Q3Hzz+R{PVe;hP0B)c|HUIzs literal 0 HcmV?d00001 From b3e5b41d5cf9563ee3081fe6f3de1738b1b3febd Mon Sep 17 00:00:00 2001 From: muzimu217 Date: Mon, 19 Oct 2026 21:48:26 -0700 Subject: [PATCH 3/3] style(host-core): apply rustfmt to the merged import list --- crates/host-core/src/rpc/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/host-core/src/rpc/mod.rs b/crates/host-core/src/rpc/mod.rs index 520556e7b3..ba6dcca291 100644 --- a/crates/host-core/src/rpc/mod.rs +++ b/crates/host-core/src/rpc/mod.rs @@ -5685,9 +5685,9 @@ mod tests { use super::{ capability_err, handle_request, index_grep_boost_enabled, parse_capability_query, parse_capability_target, peek_jsonrpc_id, provider_rpc_err, request_budget_ms, - resolve_plan_workspace, resolve_tool_workspace, resolve_tool_workspace_for_call, response_for, - rpc_err, scope_err, skill_err, validate_settings_value, with_request_budget, JsonRpcError, - RPC_REQUEST_BUDGET_MS, + resolve_plan_workspace, resolve_tool_workspace, resolve_tool_workspace_for_call, + response_for, rpc_err, scope_err, skill_err, validate_settings_value, with_request_budget, + JsonRpcError, RPC_REQUEST_BUDGET_MS, }; use crate::agent_capabilities::CapabilityLevel; use crate::plans::{PlanResolveParams, PlanSubmitParams};