Fix build errors

This commit is contained in:
Vignesh Natarajan 2026-01-27 23:08:43 -08:00
parent f3f1640c7b
commit 62e80d030c
4 changed files with 62 additions and 29 deletions

View File

@ -109,6 +109,13 @@ out to QMD for retrieval. Key points:
a release) and make sure the `qmd` binary is on the gateways `PATH`. a release) and make sure the `qmd` binary is on the gateways `PATH`.
- QMD needs an SQLite build that allows extensions (`brew install sqlite` on - QMD needs an SQLite build that allows extensions (`brew install sqlite` on
macOS). The gateway sets `INDEX_PATH`/`QMD_CONFIG_DIR` automatically. macOS). The gateway sets `INDEX_PATH`/`QMD_CONFIG_DIR` automatically.
- QMD shells out to its CLI, which depends on an Ollama daemon listening on
`http://localhost:11434` to load the bundled models (`embeddinggemma`,
`ExpedientFalcon/qwen3-reranker:0.6b-q8_0`, `qwen3:0.6b`). Install/run Ollama
separately before enabling the backend.
- OS support: macOS and Linux work out of the box once Bun + SQLite + Ollama are
installed. Windows requires WSL2 (or building the experimental Ollama port)
until Ollama ships native binaries.
**How the sidecar runs** **How the sidecar runs**
- The gateway writes a self-contained QMD home under - The gateway writes a self-contained QMD home under

View File

@ -213,13 +213,16 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) {
onMissing: (error) => defaultRuntime.log(error ?? "Memory search disabled."), onMissing: (error) => defaultRuntime.log(error ?? "Memory search disabled."),
onCloseError: (err) => onCloseError: (err) =>
defaultRuntime.error(`Memory manager close failed: ${formatErrorMessage(err)}`), defaultRuntime.error(`Memory manager close failed: ${formatErrorMessage(err)}`),
close: (manager) => manager.close(), close: async (manager) => {
await manager.close?.();
},
run: async (manager) => { run: async (manager) => {
const deep = Boolean(opts.deep || opts.index); const deep = Boolean(opts.deep || opts.index);
let embeddingProbe: let embeddingProbe:
| Awaited<ReturnType<typeof manager.probeEmbeddingAvailability>> | Awaited<ReturnType<typeof manager.probeEmbeddingAvailability>>
| undefined; | undefined;
let indexError: string | undefined; let indexError: string | undefined;
const syncFn = manager.sync;
if (deep) { if (deep) {
await withProgress({ label: "Checking memory…", total: 2 }, async (progress) => { await withProgress({ label: "Checking memory…", total: 2 }, async (progress) => {
progress.setLabel("Probing vector…"); progress.setLabel("Probing vector…");
@ -229,7 +232,7 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) {
embeddingProbe = await manager.probeEmbeddingAvailability(); embeddingProbe = await manager.probeEmbeddingAvailability();
progress.tick(); progress.tick();
}); });
if (opts.index) { if (opts.index && syncFn) {
await withProgressTotals( await withProgressTotals(
{ {
label: "Indexing memory…", label: "Indexing memory…",
@ -238,7 +241,7 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) {
}, },
async (update, progress) => { async (update, progress) => {
try { try {
await manager.sync({ await syncFn({
reason: "cli", reason: "cli",
force: true, force: true,
progress: (syncUpdate) => { progress: (syncUpdate) => {
@ -257,6 +260,8 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) {
} }
}, },
); );
} else if (opts.index && !syncFn) {
defaultRuntime.log("Memory backend does not support manual reindex.");
} }
} else { } else {
await manager.probeVectorAvailability(); await manager.probeVectorAvailability();
@ -265,11 +270,10 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) {
const sources = ( const sources = (
status.sources?.length ? status.sources : ["memory"] status.sources?.length ? status.sources : ["memory"]
) as MemorySourceName[]; ) as MemorySourceName[];
const scan = await scanMemorySources({ const workspaceDir = status.workspaceDir;
workspaceDir: status.workspaceDir, const scan = workspaceDir
agentId, ? await scanMemorySources({ workspaceDir, agentId, sources })
sources, : undefined;
});
allResults.push({ agentId, status, embeddingProbe, indexError, scan }); allResults.push({ agentId, status, embeddingProbe, indexError, scan });
}, },
}); });
@ -291,26 +295,31 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) {
for (const result of allResults) { for (const result of allResults) {
const { agentId, status, embeddingProbe, indexError, scan } = result; const { agentId, status, embeddingProbe, indexError, scan } = result;
const filesIndexed = status.files ?? 0;
const chunksIndexed = status.chunks ?? 0;
const totalFiles = scan?.totalFiles ?? null; const totalFiles = scan?.totalFiles ?? null;
const indexedLabel = const indexedLabel =
totalFiles === null totalFiles === null
? `${status.files}/? files · ${status.chunks} chunks` ? `${filesIndexed}/? files · ${chunksIndexed} chunks`
: `${status.files}/${totalFiles} files · ${status.chunks} chunks`; : `${filesIndexed}/${totalFiles} files · ${chunksIndexed} chunks`;
if (opts.index) { if (opts.index) {
const line = indexError ? `Memory index failed: ${indexError}` : "Memory index complete."; const line = indexError ? `Memory index failed: ${indexError}` : "Memory index complete.";
defaultRuntime.log(line); defaultRuntime.log(line);
} }
const requestedProvider = status.requestedProvider ?? status.provider;
const modelLabel = status.model ?? status.provider;
const storePath = status.dbPath ? shortenHomePath(status.dbPath) : "<unknown>";
const workspacePath = status.workspaceDir ? shortenHomePath(status.workspaceDir) : "<unknown>";
const sourceList = status.sources?.length ? status.sources.join(", ") : null;
const lines = [ const lines = [
`${heading("Memory Search")} ${muted(`(${agentId})`)}`, `${heading("Memory Search")} ${muted(`(${agentId})`)}`,
`${label("Provider")} ${info(status.provider)} ${muted( `${label("Provider")} ${info(status.provider)} ${muted(`(requested: ${requestedProvider})`)}`,
`(requested: ${status.requestedProvider})`, `${label("Model")} ${info(modelLabel)}`,
)}`, sourceList ? `${label("Sources")} ${info(sourceList)}` : null,
`${label("Model")} ${info(status.model)}`,
status.sources?.length ? `${label("Sources")} ${info(status.sources.join(", "))}` : null,
`${label("Indexed")} ${success(indexedLabel)}`, `${label("Indexed")} ${success(indexedLabel)}`,
`${label("Dirty")} ${status.dirty ? warn("yes") : muted("no")}`, `${label("Dirty")} ${status.dirty ? warn("yes") : muted("no")}`,
`${label("Store")} ${info(shortenHomePath(status.dbPath))}`, `${label("Store")} ${info(storePath)}`,
`${label("Workspace")} ${info(shortenHomePath(status.workspaceDir))}`, `${label("Workspace")} ${info(workspacePath)}`,
].filter(Boolean) as string[]; ].filter(Boolean) as string[];
if (embeddingProbe) { if (embeddingProbe) {
const state = embeddingProbe.ok ? "ready" : "unavailable"; const state = embeddingProbe.ok ? "ready" : "unavailable";
@ -323,7 +332,7 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) {
if (status.sourceCounts?.length) { if (status.sourceCounts?.length) {
lines.push(label("By source")); lines.push(label("By source"));
for (const entry of status.sourceCounts) { for (const entry of status.sourceCounts) {
const total = scan?.sources.find( const total = scan?.sources?.find(
(scanEntry) => scanEntry.source === entry.source, (scanEntry) => scanEntry.source === entry.source,
)?.totalFiles; )?.totalFiles;
const counts = const counts =
@ -455,9 +464,12 @@ export function registerMemoryCli(program: Command) {
onMissing: (error) => defaultRuntime.log(error ?? "Memory search disabled."), onMissing: (error) => defaultRuntime.log(error ?? "Memory search disabled."),
onCloseError: (err) => onCloseError: (err) =>
defaultRuntime.error(`Memory manager close failed: ${formatErrorMessage(err)}`), defaultRuntime.error(`Memory manager close failed: ${formatErrorMessage(err)}`),
close: (manager) => manager.close(), close: async (manager) => {
await manager.close?.();
},
run: async (manager) => { run: async (manager) => {
try { try {
const syncFn = manager.sync;
if (opts.verbose) { if (opts.verbose) {
const status = manager.status(); const status = manager.status();
const rich = isRich(); const rich = isRich();
@ -466,15 +478,17 @@ export function registerMemoryCli(program: Command) {
const info = (text: string) => colorize(rich, theme.info, text); const info = (text: string) => colorize(rich, theme.info, text);
const warn = (text: string) => colorize(rich, theme.warn, text); const warn = (text: string) => colorize(rich, theme.warn, text);
const label = (text: string) => muted(`${text}:`); const label = (text: string) => muted(`${text}:`);
const sourceLabels = status.sources.map((source) => const sourceLabels = (status.sources ?? []).map((source) =>
formatSourceLabel(source, status.workspaceDir, agentId), formatSourceLabel(source, status.workspaceDir ?? "", agentId),
); );
const requestedProvider = status.requestedProvider ?? status.provider;
const modelLabel = status.model ?? status.provider;
const lines = [ const lines = [
`${heading("Memory Index")} ${muted(`(${agentId})`)}`, `${heading("Memory Index")} ${muted(`(${agentId})`)}`,
`${label("Provider")} ${info(status.provider)} ${muted( `${label("Provider")} ${info(status.provider)} ${muted(
`(requested: ${status.requestedProvider})`, `(requested: ${requestedProvider})`,
)}`, )}`,
`${label("Model")} ${info(status.model)}`, `${label("Model")} ${info(modelLabel)}`,
sourceLabels.length sourceLabels.length
? `${label("Sources")} ${info(sourceLabels.join(", "))}` ? `${label("Sources")} ${info(sourceLabels.join(", "))}`
: null, : null,
@ -514,6 +528,10 @@ export function registerMemoryCli(program: Command) {
? `${lastLabel} · elapsed ${elapsed} · eta ${eta}` ? `${lastLabel} · elapsed ${elapsed} · eta ${eta}`
: `${lastLabel} · elapsed ${elapsed}`; : `${lastLabel} · elapsed ${elapsed}`;
}; };
if (!syncFn) {
defaultRuntime.log("Memory backend does not support manual reindex.");
return;
}
await withProgressTotals( await withProgressTotals(
{ {
label: "Indexing memory…", label: "Indexing memory…",
@ -525,7 +543,7 @@ export function registerMemoryCli(program: Command) {
progress.setLabel(buildLabel()); progress.setLabel(buildLabel());
}, 1000); }, 1000);
try { try {
await manager.sync({ await syncFn({
reason: "cli", reason: "cli",
force: true, force: true,
progress: (syncUpdate) => { progress: (syncUpdate) => {
@ -579,7 +597,9 @@ export function registerMemoryCli(program: Command) {
onMissing: (error) => defaultRuntime.log(error ?? "Memory search disabled."), onMissing: (error) => defaultRuntime.log(error ?? "Memory search disabled."),
onCloseError: (err) => onCloseError: (err) =>
defaultRuntime.error(`Memory manager close failed: ${formatErrorMessage(err)}`), defaultRuntime.error(`Memory manager close failed: ${formatErrorMessage(err)}`),
close: (manager) => manager.close(), close: async (manager) => {
await manager.close?.();
},
run: async (manager) => { run: async (manager) => {
let results: Awaited<ReturnType<typeof manager.search>>; let results: Awaited<ReturnType<typeof manager.search>>;
try { try {

View File

@ -84,10 +84,14 @@ export async function resolveDiscordTarget(
const likelyUsername = isLikelyUsername(trimmed); const likelyUsername = isLikelyUsername(trimmed);
const shouldLookup = isExplicitUserLookup(trimmed, parseOptions) || likelyUsername; const shouldLookup = isExplicitUserLookup(trimmed, parseOptions) || likelyUsername;
// Parse directly if it's already a known format. Use a safe parse so ambiguous
// numeric targets don't throw when we still want to attempt username lookup.
const directParse = safeParseDiscordTarget(trimmed, parseOptions); const directParse = safeParseDiscordTarget(trimmed, parseOptions);
if (directParse && directParse.kind !== "channel" && !likelyUsername) { if (directParse && directParse.kind !== "channel" && !likelyUsername) {
return directParse; return directParse;
} }
if (!shouldLookup) { if (!shouldLookup) {
return directParse ?? parseDiscordTarget(trimmed, parseOptions); return directParse ?? parseDiscordTarget(trimmed, parseOptions);
} }

View File

@ -23,6 +23,8 @@ import type {
MemorySource, MemorySource,
MemorySyncProgressUpdate, MemorySyncProgressUpdate,
} from "./types.js"; } from "./types.js";
type SqliteDatabase = import("node:sqlite").DatabaseSync;
import type { ResolvedMemoryBackendConfig, ResolvedQmdConfig } from "./backend-config.js"; import type { ResolvedMemoryBackendConfig, ResolvedQmdConfig } from "./backend-config.js";
const log = createSubsystemLogger("memory"); const log = createSubsystemLogger("memory");
@ -85,7 +87,7 @@ export class QmdMemoryManager implements MemorySearchManager {
private updateTimer: NodeJS.Timeout | null = null; private updateTimer: NodeJS.Timeout | null = null;
private pendingUpdate: Promise<void> | null = null; private pendingUpdate: Promise<void> | null = null;
private closed = false; private closed = false;
private db: import("node:sqlite").DatabaseSync | null = null; private db: SqliteDatabase | null = null;
private lastUpdateAt: number | null = null; private lastUpdateAt: number | null = null;
private constructor(params: { private constructor(params: {
@ -379,10 +381,10 @@ export class QmdMemoryManager implements MemorySearchManager {
}); });
} }
private ensureDb() { private ensureDb(): SqliteDatabase {
if (this.db) return this.db; if (this.db) return this.db;
const sqlite = requireNodeSqlite(); const { DatabaseSync } = requireNodeSqlite();
this.db = sqlite.open(this.indexPath, { readonly: true }); this.db = new DatabaseSync(this.indexPath, { readOnly: true });
return this.db; return this.db;
} }