Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 66 additions & 1 deletion server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
// dependency to rebuild against bb's embedded runtime on every app update).
// Writes go through `bb memory` — the public surface — so content guards,
// version history and optimistic concurrency apply exactly as they do to agents.
import { execFile } from "node:child_process";
import { execFile, execFileSync } from "node:child_process";
import os from "node:os";
import { promisify } from "node:util";
import { defineRpcContract, type BbPluginApi } from "@bb/plugin-sdk";
Expand Down Expand Up @@ -351,7 +351,72 @@ async function bbMemory(args: string[], projectId: string | null) {
}
}

/**
* Which commit is this PROCESS running? (MX-139/MX-141)
*
* bb bundles a `path:` plugin FROM SOURCE at reload, so a revision read here — at module
* load, the same moment — is by construction the code now executing. Nothing else can say:
* `bb plugin list` prints `running` and the source path but no revision, `bb plugin source`
* has none to record for a path: source, and dist/ is NOT the loaded artifact (its mtime was
* measured lying by 15 minutes). So a checkout can sit clean on main, every drift check
* green, while the process runs something older.
*
* Synchronous on purpose: the value must be fixed before anything can observe it, and it is
* one git call per load. Failure yields rev: null rather than a guess — a tarball install has
* no git dir, and that must stay distinguishable from a real mismatch so a checker reports
* UNKNOWN rather than OK. `dirty` rides along because a bundle built from an edited tree
* matches NO commit, and comparing revisions alone would call that a match.
*/
const BUILD_STAMP: { rev: string | null; dirty: boolean | null; sourceDir: string; loadedAt: string; why: string | null } = (() => {
const here = import.meta.dirname;
const loadedAt = new Date().toISOString();
try {
const git = (args: string[]): string =>
execFileSync("git", ["-C", here, ...args], { encoding: "utf8", timeout: 5000 }).trim();
return {
rev: git(["rev-parse", "HEAD"]),
dirty: git(["status", "--porcelain"]).length > 0,
sourceDir: git(["rev-parse", "--show-toplevel"]),
loadedAt,
why: null,
};
} catch (e) {
return { rev: null, dirty: null, sourceDir: here, loadedAt, why: e instanceof Error ? e.message : String(e) };
}
})();

export default async function plugin(bb: BbPluginApi) {
// This plugin is a UI surface — until now it registered only `bb.rpc` and had no CLI at
// all, so `bb memory-ui` was genuinely "unknown command". That also made it the one
// plugin whose RUNNING revision could not be asked for from a shell, and so the one the
// loaded-revision check in dotfiles could never cover.
//
// Registering a single introspection command rather than a whole surface: `build` is not
// a feature, it is the answer to "is what I merged what is running".
bb.cli.register({
name: "memory-ui",
summary: "The memory browser UI (no interactive commands — this surface is introspection only)",
commands: [
{
name: "build",
summary: "Which commit this RUNNING process was loaded from (not the checkout)",
usage: "bb memory-ui build [--json]",
},
],
async run(argv) {
if (argv[0] === "build" || argv.length === 0) {
if (argv.includes("--json")) return { exitCode: 0, stdout: JSON.stringify(BUILD_STAMP) };
const dirty = BUILD_STAMP.dirty === null ? "" : BUILD_STAMP.dirty ? " +dirty" : "";
const why = BUILD_STAMP.why ? ` (${BUILD_STAMP.why})` : "";
return {
exitCode: 0,
stdout: `loaded ${BUILD_STAMP.rev ?? "unknown"}${dirty} from ${BUILD_STAMP.sourceDir} at ${BUILD_STAMP.loadedAt}${why}`,
};
}
return { exitCode: 1, stderr: `unknown subcommand: ${argv[0]} — only 'build' exists` };
},
});

const titleCache = new Map<string, string | null>();

const viewClause = (view: string) =>
Expand Down
Loading