Imported from fdarian/nisi (
packages/sidecar-api/AGENTS.md). Install upstream withnpx skills add fdarian/nisi --skill sidecar-api. Copyright stays with the author.
@repo/sidecar-api
Contract-first oRPC v2 contract for the desktop sidecar's wire API — the single source of truth shared
by the sidecar (implementer, @repo/desktop) and its frontend client.
Git/review procedures sit alongside health.check.
health.ts,sessions.ts,diff.ts,review.ts,events.ts,walkthrough.ts,chat.ts,settings.ts,code-index.ts— schema + procedure contract per domain (packages/sidecar-api/src/<domain>.ts), each just types — no git/SQLite/agent logic lives here, that's@repo/git/@repo/review/@repo/walkthrough/@repo/harness-local/@repo/settings/@repo/code-lsp, consumed only by the sidecar's implementation.walkthrough.tsredeclares@repo/walkthrough'sLocation/ReferenceBlock/Section/Walkthroughrather than importing them, same asdiff.tsmirrors@repo/git'sFileChange— this package stays dependency-free from every domain package.settings.tsandchat.tsare the exception to "mirrors a domain package's type": there's noHarnessIdin any domain package to mirror (@repo/settingsdeliberately stores it as a loosestring[], per its own AGENTS.md), soHarnessIdhere is sidecar-api's own invention, defined once inwalkthrough.tsand imported by both rather than redeclared.code-index.ts'ssymbolKeyfields are opaque strings — an encodedpath:line:charposition (apps/desktop/sidecar/code-index/state.ts'sencodeSymbolKey/decodeSymbolKey), not a type this package or@repo/code-lspnames anywhere — meaningful only ascodeIndex.references' input, never parsed client-side.contract.ts— composes domain contracts into the router; owns the two@orpc/experimental-effect/extensions/*side-effect imports. These must run before any domain module callsoc.input()/oc.output()— every domain module is imported only from here, never directly, whichpackage.json's narrowexportsmap ("." -> "./src/index.ts") enforces from outside this package.client.ts—makeSidecarClient({ port, token }), a typedRouterContractClient.
Gotchas
effecthere is thebetadist-tag (4.0.0-beta.x) —lateston npm is still v3. Pinned exact, not^.oc.input()/oc.output()accept an EffectSchemadirectly (that's what the extension imports incontract.tsbuy you) — but helpers outside that patched surface, likeeventIterator(events.ts), still want a Standard Schema. Convert withSchema.toStandardSchemaV1(...).diff.fileContentsis batched (paths: FileContentRequest[]in,FileContentResult[]out, one per requested path) rather than one-path-per-call — it replaced a singulardiff.fileoutright (its only caller,apps/desktop/src/lib/pr-data.ts'suseFileContents, chunks a large PR's paths across several calls rather than issuing one per file). A path not actually in the diff reportscontent: nullin its own result entry instead of failing the batch. Each path'sforceinput field exists so the load-on-demand size tier (see@repo/git) has any way to actually be loaded.diff.files/diff.fileContentsboth gainedincludeUncommitted, mirroring@repo/git's option of the same name — the frontend sources it from@repo/settings's persisted setting and folds it into the queryinput(not a separate param) specifically so it's part of the TanStack Query cache key; seeapps/desktop/src/lib/pr-data.ts'suseFileChanges/useFileContents.fileContents' flag sits at the batch's top level, not per-path inFileContentRequest— it mirrors a session-wide setting applied uniformly, the same reasoning@repo/git'sgetFileContentsresolves it to oneDiffTargetfor the whole call rather than per-path.- Phase 3's range-scoped review added
review.setRangeViewed(mirrorssetViewed's tick/untick shape, scoped to one block's claim on a set of ranges within one file) anddiff.ts'sReviewRangegainedreviewedVia: ReviewSource | null—{kind: "file"}or{kind: "range", blockId, blockLabel}, attributing each surviving range to the claim currently covering it.FileContentReviewis now populated whenever a file has any active claim, not only once it's been whole-file-ticked.rangesitself now only feeds the walkthrough reference pane's per-file reviewed/partial/unreviewed checkbox (apps/desktop/src/components/walkthrough/reference-pane.tsx) — the diff pane stopped reading it oncebaselineKindshipped, below. FileContentReviewgainedbaselineKind: "base" | "reviewed", telling the diff pane which fileFileContent.patch/oldContentare actually diffed against."reviewed"means the sidecar substituted@repo/review's synthesizedreviewedBaselinefor the usual merge-base content before computing the patch — so an empty patch under"reviewed"means "nothing new since your last pass," not "nothing changed in the PR." Always"base"for a size-gated file even with an active claim, since reconciliation needs content the size gate withheld.HarnessInfogainedavailable/binaryPath(a live@repo/bin-resolvercheck — see the type's own doc for why this is independent ofenabled), andwalkthrough.harnessesgained a siblingwalkthrough.refreshHarnesses— same output shape, but bypassesmodel-discovery.ts's cache. A separate procedure rather than aforceinput field (unlikediff.fileContents's, above) so the UI can keep one stable, shared query-cache entry forharnesseswhilerefreshHarnessesis called imperatively and its result written back into that same cache — seeapps/desktop/src/lib/walkthrough-data.ts'suseHarnesses.