zz_

app/cli/lib/remoteBackend.ts

zzmq.dev@gmail.com/zz / app / cli / lib / remoteBackend.ts @ cc411457 Download
Ladder Summary
  • file The file defines a **read‑only RemoteBackend** implementation that forwards all supported backend reads (file content, anchors, ladder trees, lanes, lane lists, and ladder searches) to the server via `callRemoteTool` or a direct `apiFetch` call, while deliberately throwing errors for any write‑oriented methods to enforce server‑side atomicity. It exports a single constant **`remoteBackend`** (type `Backend`) that implements the backend interface, plus an internal helper **`notSupported`** used to reject unsupported mutation methods.
 // A deliberately MINIMAL Backend (engine/backend.ts) for `zz work`
 // operating against a deployed Worker's real protocol state --
 // read-only. Every mutation (ask_claim, ask_post, ask_answer,
 // commit_changeset, ladder_write_rung, lane_propose) goes through
 // callRemoteTool directly instead of through this Backend's write
 // methods, because the server's real atomicity (MaintainerDO's
 // storage.transactionSync-backed withLane) only exists server-side --
 // a client-side read-modify-write here would race against other
 // agents working the same repo concurrently, exactly the failure mode
 // this whole project exists to prevent. Reads are safe either way, so
 // this Backend only implements those; its write methods throw loudly
 // rather than silently doing something non-atomic, so any new code
 // path that tries to use one fails fast instead of corrupting state
 // under real concurrency.
 //
 // Not constructed per-repo: every Backend method already takes `repo`
 // as its own first argument (claimableAcrossRepos iterates several
 // repos through the SAME Backend instance), so this just forwards
 // whichever repo each call names, rather than baking one in.
 import type { Backend, LadderSearchHit } from "../../engine/backend.js";
 import type { Lane } from "../../engine/types.js";
 import type { EmbeddedRung } from "../../engine/ladderNotes.js";
 import type { ResolvedRung } from "../../engine/ladderRender.js";
 import { callRemoteTool } from "./remoteTools.js";
 import { apiFetch } from "./api.js";
 
 function notSupported(method: string): never {
  throw new Error(
  `RemoteBackend.${method}: not implemented on purpose -- mutations go through callRemoteTool (the real protocol tools: ask_post/ask_claim/ask_answer/commit_changeset/ladder_write_rung/lane_propose), never a raw Backend write, since only the server side can make a Lane mutation atomic against other concurrently-working agents.`,
  );
 }
 
 export const remoteBackend: Backend = {
  name: "remote",
  async ensureCloned() {
  // No-op here -- cli/lib/repo.ts's own mode-aware ensureCloned
  // handles getting a real local working copy (via the git smart-
  // HTTP proxy) for run_in_sandbox/threeWayMerge to use; this
  // Backend is only the Lane/Ladder/git-content read surface.
  },
  listCommits: () => notSupported("listCommits"),
  diffCommit: () => notSupported("diffCommit"),
  async readFileAtCommit(repo, sha, path) {
  return (await callRemoteTool(repo, "git_show_file", { sha, path })) as string;
  },
  commitChangeset: () => notSupported("commitChangeset"),
  async readAnchor(repo, path) {
  return (await callRemoteTool(repo, "ladder_read", { path })) as EmbeddedRung | null;
  },
  // One tool call, server-side resolved and cached -- NOT the client-
  // side resolveLadderTree(readAnchor) walk this repo used to drive
  // (tui/useLadderTrees.ts), which meant one real HTTP round trip per
  // rung. Confirmed live: that was the actual cause of `zz top`'s
  // Ladder tab being unusably slow, and would only get worse on a
  // repo with many files/changesets -- this collapses it to one call
  // regardless of tree size, server-side cached so repeat polls of an
  // unchanged tree are nearly free.
  async readLadderTree(repo) {
  return (await callRemoteTool(repo, "ladder_tree", {})) as ResolvedRung | null;
  },
  writeLadder: () => notSupported("writeLadder"),
  appendRepoChildren: () => notSupported("appendRepoChildren"),
  async readLane(repo, laneId) {
  return (await callRemoteTool(repo, "lane_get", { lane_id: laneId })) as Lane | null;
  },
  writeLane: () => notSupported("writeLane"),
  async listLanes(repo) {
  return (await callRemoteTool(repo, "lane_list", {})) as Lane[];
  },
  withLane: () => notSupported("withLane"),
  // Cross-repo, so callRemoteTool (which routes to one specific
  // repo's DO) doesn't apply here -- a direct API call instead, same
  // as any other plain CLI->Worker call. Safe specifically because
  // this file (cli/lib/remoteBackend.ts) only ever runs in the CLI's
  // own process, never bundled into the Worker -- unlike
  // agent/tools.ts's ladder_search tool, whose execute() runs
  // server-side when dispatched through the generic remote
  // tool-calling loop and so can't make this same kind of call.
  async searchLadder(query, limit) {
  const { hits } = await apiFetch<{ hits: { repo_slug: string; path: string; kind: string; commit_sha: string | null; summary: string }[] }>("GET", `/api/ladder/search?q=${encodeURIComponent(query)}&limit=${limit}`);
  return hits.map((h) => ({ repo: h.repo_slug, path: h.path, kind: h.kind as LadderSearchHit["kind"], commitSha: h.commit_sha, summary: h.summary }));
  },
  // Repo-scoped (unlike searchLadder), so callRemoteTool applies here --
  // routes to that one repo's DO, same as any other mutation-shaped call.
  async reindexLadder(repo) {
  const { indexed } = (await callRemoteTool(repo, "ladder_reindex", { repo })) as { indexed: number };
  return indexed;
  },
 };