chore: remove verified dead code paths

This commit is contained in:
Peter Steinberger
2026-02-22 09:21:00 +01:00
parent b967687e55
commit d06ad6bc55
18 changed files with 0 additions and 878 deletions

View File

@@ -1,4 +0,0 @@
export { serveAcpGateway } from "./server.js";
export { createInMemorySessionStore } from "./session.js";
export type { AcpSessionStore } from "./session.js";
export type { AcpServerOptions } from "./types.js";

View File

@@ -1,68 +0,0 @@
import type { AgentEvent } from "@mariozechner/pi-agent-core";
import type { Mock } from "vitest";
import {
handleToolExecutionEnd,
handleToolExecutionStart,
} from "./pi-embedded-subscribe.handlers.tools.js";
import type { EmbeddedPiSubscribeContext } from "./pi-embedded-subscribe.handlers.types.js";
import type { SubscribeEmbeddedPiSessionParams } from "./pi-embedded-subscribe.types.js";
/**
* Narrowed params type that omits the `session` class instance (never accessed
* by the handler paths under test).
*/
type TestParams = Omit<SubscribeEmbeddedPiSessionParams, "session">;
/**
* The subset of {@link EmbeddedPiSubscribeContext} that the media-emission
* tests actually populate. Using this avoids the need for `as unknown as`
* double-assertion in every mock factory.
*/
export type MockEmbeddedContext = Omit<EmbeddedPiSubscribeContext, "params"> & {
params: TestParams;
};
/** Type-safe bridge: narrows parameter type so callers avoid assertions. */
function asFullContext(ctx: MockEmbeddedContext): EmbeddedPiSubscribeContext {
return ctx as unknown as EmbeddedPiSubscribeContext;
}
/** Typed wrapper around {@link handleToolExecutionStart}. */
export function callToolExecutionStart(
ctx: MockEmbeddedContext,
evt: AgentEvent & { toolName: string; toolCallId: string; args: unknown },
): Promise<void> {
return handleToolExecutionStart(asFullContext(ctx), evt);
}
/** Typed wrapper around {@link handleToolExecutionEnd}. */
export function callToolExecutionEnd(
ctx: MockEmbeddedContext,
evt: AgentEvent & {
toolName: string;
toolCallId: string;
isError: boolean;
result?: unknown;
},
): Promise<void> {
return handleToolExecutionEnd(asFullContext(ctx), evt);
}
/**
* Check whether a mock-call argument is an object containing `mediaUrls`
* but NOT `text` (i.e. a "direct media" emission).
*/
export function isDirectMediaCall(call: unknown[]): boolean {
const arg = call[0];
if (!arg || typeof arg !== "object") {
return false;
}
return "mediaUrls" in arg && !("text" in arg);
}
/**
* Filter a vi.fn() mock's call log to only direct-media emissions.
*/
export function filterDirectMediaCalls(mock: Mock): unknown[][] {
return mock.mock.calls.filter(isDirectMediaCall);
}

View File

@@ -1,208 +0,0 @@
import type { OpenClawConfig } from "../../config/config.js";
import { callGateway, randomIdempotencyKey } from "../../gateway/call.js";
import { logVerbose } from "../../globals.js";
import type { CommandHandler } from "./commands-types.js";
type NodeSummary = {
nodeId: string;
displayName?: string;
platform?: string;
deviceFamily?: string;
remoteIp?: string;
connected?: boolean;
};
const PTT_COMMANDS: Record<string, string> = {
start: "talk.ptt.start",
stop: "talk.ptt.stop",
once: "talk.ptt.once",
cancel: "talk.ptt.cancel",
};
function normalizeNodeKey(value: string) {
return value
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+/, "")
.replace(/-+$/, "");
}
function isIOSNode(node: NodeSummary): boolean {
const platform = node.platform?.toLowerCase() ?? "";
const family = node.deviceFamily?.toLowerCase() ?? "";
return (
platform.startsWith("ios") ||
family.includes("iphone") ||
family.includes("ipad") ||
family.includes("ios")
);
}
async function loadNodes(cfg: OpenClawConfig): Promise<NodeSummary[]> {
try {
const res = await callGateway<{ nodes?: NodeSummary[] }>({
method: "node.list",
params: {},
config: cfg,
});
return Array.isArray(res.nodes) ? res.nodes : [];
} catch {
const res = await callGateway<{ pending?: unknown[]; paired?: NodeSummary[] }>({
method: "node.pair.list",
params: {},
config: cfg,
});
return Array.isArray(res.paired) ? res.paired : [];
}
}
function describeNodes(nodes: NodeSummary[]) {
return nodes
.map((node) => node.displayName || node.remoteIp || node.nodeId)
.filter(Boolean)
.join(", ");
}
function resolveNodeId(nodes: NodeSummary[], query?: string): string {
const trimmed = String(query ?? "").trim();
if (trimmed) {
const qNorm = normalizeNodeKey(trimmed);
const matches = nodes.filter((node) => {
if (node.nodeId === trimmed) {
return true;
}
if (typeof node.remoteIp === "string" && node.remoteIp === trimmed) {
return true;
}
const name = typeof node.displayName === "string" ? node.displayName : "";
if (name && normalizeNodeKey(name) === qNorm) {
return true;
}
if (trimmed.length >= 6 && node.nodeId.startsWith(trimmed)) {
return true;
}
return false;
});
if (matches.length === 1) {
return matches[0].nodeId;
}
const known = describeNodes(nodes);
if (matches.length === 0) {
throw new Error(`unknown node: ${trimmed}${known ? ` (known: ${known})` : ""}`);
}
throw new Error(
`ambiguous node: ${trimmed} (matches: ${matches
.map((node) => node.displayName || node.remoteIp || node.nodeId)
.join(", ")})`,
);
}
const iosNodes = nodes.filter(isIOSNode);
const iosConnected = iosNodes.filter((node) => node.connected);
const iosCandidates = iosConnected.length > 0 ? iosConnected : iosNodes;
if (iosCandidates.length === 1) {
return iosCandidates[0].nodeId;
}
if (iosCandidates.length > 1) {
throw new Error(
`multiple iOS nodes found (${describeNodes(iosCandidates)}); specify node=<id>`,
);
}
const connected = nodes.filter((node) => node.connected);
const fallback = connected.length > 0 ? connected : nodes;
if (fallback.length === 1) {
return fallback[0].nodeId;
}
const known = describeNodes(nodes);
throw new Error(`node required${known ? ` (known: ${known})` : ""}`);
}
function parsePTTArgs(commandBody: string) {
const tokens = commandBody.trim().split(/\s+/).slice(1);
let action: string | undefined;
let node: string | undefined;
for (const token of tokens) {
if (!token) {
continue;
}
if (token.toLowerCase().startsWith("node=")) {
node = token.slice("node=".length);
continue;
}
if (!action) {
action = token;
}
}
return { action, node };
}
function buildPTTHelpText() {
return [
"Usage: /ptt <start|stop|once|cancel> [node=<id>]",
"Example: /ptt once node=iphone",
].join("\n");
}
export const handlePTTCommand: CommandHandler = async (params, allowTextCommands) => {
if (!allowTextCommands) {
return null;
}
const { command, cfg } = params;
const normalized = command.commandBodyNormalized.trim();
if (!normalized.startsWith("/ptt")) {
return null;
}
if (!command.isAuthorizedSender) {
logVerbose(`Ignoring /ptt from unauthorized sender: ${command.senderId || "<unknown>"}`);
return { shouldContinue: false, reply: { text: "PTT requires an authorized sender." } };
}
const parsed = parsePTTArgs(normalized);
const actionKey = parsed.action?.trim().toLowerCase() ?? "";
const commandId = PTT_COMMANDS[actionKey];
if (!commandId) {
return { shouldContinue: false, reply: { text: buildPTTHelpText() } };
}
try {
const nodes = await loadNodes(cfg);
const nodeId = resolveNodeId(nodes, parsed.node);
const invokeParams: Record<string, unknown> = {
nodeId,
command: commandId,
params: {},
idempotencyKey: randomIdempotencyKey(),
timeoutMs: 15_000,
};
const res = await callGateway<{
ok?: boolean;
payload?: Record<string, unknown>;
command?: string;
nodeId?: string;
}>({
method: "node.invoke",
params: invokeParams,
config: cfg,
});
const payload = res.payload && typeof res.payload === "object" ? res.payload : {};
const lines = [`PTT ${actionKey}${nodeId}`];
if (typeof payload.status === "string") {
lines.push(`status: ${payload.status}`);
}
if (typeof payload.captureId === "string") {
lines.push(`captureId: ${payload.captureId}`);
}
if (typeof payload.transcript === "string" && payload.transcript.trim()) {
lines.push(`transcript: ${payload.transcript}`);
}
return { shouldContinue: false, reply: { text: lines.join("\n") } };
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
return { shouldContinue: false, reply: { text: `PTT failed: ${message}` } };
}
};

View File

@@ -1,2 +0,0 @@
export { monitorDiscordProvider } from "./monitor.js";
export { sendMessageDiscord, sendPollDiscord } from "./send.js";

View File

@@ -1,3 +0,0 @@
export { monitorIMessageProvider } from "./monitor.js";
export { probeIMessage } from "./probe.js";
export { sendMessageIMessage } from "./send.js";

View File

@@ -1,49 +0,0 @@
import type { IncomingMessage, ServerResponse } from "node:http";
export type LineHttpRequestHandler = (
req: IncomingMessage,
res: ServerResponse,
) => Promise<void> | void;
type RegisterLineHttpHandlerArgs = {
path?: string | null;
handler: LineHttpRequestHandler;
log?: (message: string) => void;
accountId?: string;
};
const lineHttpRoutes = new Map<string, LineHttpRequestHandler>();
export function normalizeLineWebhookPath(path?: string | null): string {
const trimmed = path?.trim();
if (!trimmed) {
return "/line/webhook";
}
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
}
export function registerLineHttpHandler(params: RegisterLineHttpHandlerArgs): () => void {
const normalizedPath = normalizeLineWebhookPath(params.path);
if (lineHttpRoutes.has(normalizedPath)) {
const suffix = params.accountId ? ` for account "${params.accountId}"` : "";
params.log?.(`line: webhook path ${normalizedPath} already registered${suffix}`);
return () => {};
}
lineHttpRoutes.set(normalizedPath, params.handler);
return () => {
lineHttpRoutes.delete(normalizedPath);
};
}
export async function handleLineHttpRequest(
req: IncomingMessage,
res: ServerResponse,
): Promise<boolean> {
const url = new URL(req.url ?? "/", "http://localhost");
const handler = lineHttpRoutes.get(url.pathname);
if (!handler) {
return false;
}
await handler(req, res);
return true;
}

View File

@@ -1,155 +0,0 @@
export {
createLineBot,
createLineWebhookCallback,
type LineBot,
type LineBotOptions,
} from "./bot.js";
export {
monitorLineProvider,
getLineRuntimeState,
type MonitorLineProviderOptions,
type LineProviderMonitor,
} from "./monitor.js";
export {
sendMessageLine,
pushMessageLine,
pushMessagesLine,
replyMessageLine,
createImageMessage,
createLocationMessage,
createFlexMessage,
createQuickReplyItems,
createTextMessageWithQuickReplies,
showLoadingAnimation,
getUserProfile,
getUserDisplayName,
pushImageMessage,
pushLocationMessage,
pushFlexMessage,
pushTemplateMessage,
pushTextMessageWithQuickReplies,
} from "./send.js";
export {
startLineWebhook,
createLineWebhookMiddleware,
type LineWebhookOptions,
type StartLineWebhookOptions,
} from "./webhook.js";
export {
handleLineHttpRequest,
registerLineHttpHandler,
normalizeLineWebhookPath,
} from "./http-registry.js";
export {
resolveLineAccount,
listLineAccountIds,
resolveDefaultLineAccountId,
normalizeAccountId,
DEFAULT_ACCOUNT_ID,
} from "./accounts.js";
export { probeLineBot } from "./probe.js";
export { downloadLineMedia } from "./download.js";
export { LineConfigSchema, type LineConfigSchemaType } from "./config-schema.js";
export { buildLineMessageContext } from "./bot-message-context.js";
export { handleLineWebhookEvents, type LineHandlerContext } from "./bot-handlers.js";
// Flex Message templates
export {
createInfoCard,
createListCard,
createImageCard,
createActionCard,
createCarousel,
createNotificationBubble,
createReceiptCard,
createEventCard,
createMediaPlayerCard,
createAppleTvRemoteCard,
createDeviceControlCard,
toFlexMessage,
type ListItem,
type CardAction,
type FlexContainer,
type FlexBubble,
type FlexCarousel,
} from "./flex-templates.js";
// Markdown to LINE conversion
export {
processLineMessage,
hasMarkdownToConvert,
stripMarkdown,
extractMarkdownTables,
extractCodeBlocks,
extractLinks,
convertTableToFlexBubble,
convertCodeBlockToFlexBubble,
convertLinksToFlexBubble,
type ProcessedLineMessage,
type MarkdownTable,
type CodeBlock,
type MarkdownLink,
} from "./markdown-to-line.js";
// Rich Menu operations
export {
createRichMenu,
uploadRichMenuImage,
setDefaultRichMenu,
cancelDefaultRichMenu,
getDefaultRichMenuId,
linkRichMenuToUser,
linkRichMenuToUsers,
unlinkRichMenuFromUser,
unlinkRichMenuFromUsers,
getRichMenuIdOfUser,
getRichMenuList,
getRichMenu,
deleteRichMenu,
createRichMenuAlias,
deleteRichMenuAlias,
createGridLayout,
messageAction,
uriAction,
postbackAction,
datetimePickerAction,
createDefaultMenuConfig,
type CreateRichMenuParams,
type RichMenuSize,
type RichMenuAreaRequest,
} from "./rich-menu.js";
// Template messages (Button, Confirm, Carousel)
export {
createConfirmTemplate,
createButtonTemplate,
createTemplateCarousel,
createCarouselColumn,
createImageCarousel,
createImageCarouselColumn,
createYesNoConfirm,
createButtonMenu,
createLinkMenu,
createProductCarousel,
messageAction as templateMessageAction,
uriAction as templateUriAction,
postbackAction as templatePostbackAction,
datetimePickerAction as templateDatetimePickerAction,
type TemplateMessage,
type ConfirmTemplate,
type ButtonsTemplate,
type CarouselTemplate,
type CarouselColumn,
} from "./template-messages.js";
export type {
LineConfig,
LineAccountConfig,
LineGroupConfig,
ResolvedLineAccount,
LineTokenSource,
LineMessageType,
LineWebhookContext,
LineSendResult,
LineProbeResult,
} from "./types.js";

View File

@@ -1,4 +0,0 @@
export { applyLinkUnderstanding } from "./apply.js";
export { extractLinksFromMessage } from "./detect.js";
export { formatLinkUnderstandingBody } from "./format.js";
export { runLinkUnderstanding } from "./runner.js";

View File

@@ -1,9 +0,0 @@
export { applyMediaUnderstanding } from "./apply.js";
export { formatMediaUnderstandingBody } from "./format.js";
export { resolveMediaUnderstandingScope } from "./scope.js";
export type {
MediaAttachment,
MediaUnderstandingOutput,
MediaUnderstandingProvider,
MediaUnderstandingKind,
} from "./types.js";

View File

@@ -1,19 +0,0 @@
function normalizeHeaderName(name: string): string {
return name.trim().toLowerCase();
}
export function fingerprintHeaderNames(headers: Record<string, string> | undefined): string[] {
if (!headers) {
return [];
}
const out: string[] = [];
for (const key of Object.keys(headers)) {
const normalized = normalizeHeaderName(key);
if (!normalized) {
continue;
}
out.push(normalized);
}
out.sort((a, b) => a.localeCompare(b));
return out;
}

View File

@@ -1,54 +0,0 @@
import type { ResolvedMemorySearchConfig } from "../agents/memory-search.js";
import { fingerprintHeaderNames } from "./headers-fingerprint.js";
import { hashText } from "./internal.js";
export function computeMemoryManagerCacheKey(params: {
agentId: string;
workspaceDir: string;
settings: ResolvedMemorySearchConfig;
}): string {
const settings = params.settings;
const fingerprint = hashText(
JSON.stringify({
enabled: settings.enabled,
sources: [...settings.sources].toSorted((a, b) => a.localeCompare(b)),
extraPaths: [...settings.extraPaths].toSorted((a, b) => a.localeCompare(b)),
provider: settings.provider,
model: settings.model,
fallback: settings.fallback,
local: {
modelPath: settings.local.modelPath,
modelCacheDir: settings.local.modelCacheDir,
},
remote: settings.remote
? {
baseUrl: settings.remote.baseUrl,
headerNames: fingerprintHeaderNames(settings.remote.headers),
batch: settings.remote.batch
? {
enabled: settings.remote.batch.enabled,
wait: settings.remote.batch.wait,
concurrency: settings.remote.batch.concurrency,
pollIntervalMs: settings.remote.batch.pollIntervalMs,
timeoutMinutes: settings.remote.batch.timeoutMinutes,
}
: undefined,
}
: undefined,
experimental: settings.experimental,
store: {
driver: settings.store.driver,
path: settings.store.path,
vector: {
enabled: settings.store.vector.enabled,
extensionPath: settings.store.vector.extensionPath,
},
},
chunking: settings.chunking,
sync: settings.sync,
query: settings.query,
cache: settings.cache,
}),
);
return `${params.agentId}:${params.workspaceDir}:${fingerprint}`;
}

View File

@@ -1,2 +0,0 @@
// Deprecated: use ./batch-openai.js
export * from "./batch-openai.js";

View File

@@ -1,33 +0,0 @@
import { fingerprintHeaderNames } from "./headers-fingerprint.js";
import { hashText } from "./internal.js";
export function computeEmbeddingProviderKey(params: {
providerId: string;
providerModel: string;
openAi?: { baseUrl: string; model: string; headers: Record<string, string> };
gemini?: { baseUrl: string; model: string; headers: Record<string, string> };
}): string {
if (params.openAi) {
const headerNames = fingerprintHeaderNames(params.openAi.headers);
return hashText(
JSON.stringify({
provider: "openai",
baseUrl: params.openAi.baseUrl,
model: params.openAi.model,
headerNames,
}),
);
}
if (params.gemini) {
const headerNames = fingerprintHeaderNames(params.gemini.headers);
return hashText(
JSON.stringify({
provider: "gemini",
baseUrl: params.gemini.baseUrl,
model: params.gemini.model,
headerNames,
}),
);
}
return hashText(JSON.stringify({ provider: params.providerId, model: params.providerModel }));
}

View File

@@ -1,39 +0,0 @@
import type { DatabaseSync } from "node:sqlite";
type SyncProgress = {
completed: number;
total: number;
report: (update: { completed: number; total: number; label?: string }) => void;
};
function tickProgress(progress: SyncProgress | undefined): void {
if (!progress) {
return;
}
progress.completed += 1;
progress.report({
completed: progress.completed,
total: progress.total,
});
}
export async function indexFileEntryIfChanged<
TEntry extends { path: string; hash: string },
>(params: {
db: DatabaseSync;
source: string;
needsFullReindex: boolean;
entry: TEntry;
indexFile: (entry: TEntry) => Promise<void>;
progress?: SyncProgress;
}): Promise<void> {
const record = params.db
.prepare(`SELECT hash FROM files WHERE path = ? AND source = ?`)
.get(params.entry.path, params.source) as { hash: string } | undefined;
if (!params.needsFullReindex && record?.hash === params.entry.hash) {
tickProgress(params.progress);
return;
}
await params.indexFile(params.entry);
tickProgress(params.progress);
}

View File

@@ -1,68 +0,0 @@
import type { DatabaseSync } from "node:sqlite";
import { createSubsystemLogger } from "../logging/subsystem.js";
import { buildFileEntry, listMemoryFiles, type MemoryFileEntry } from "./internal.js";
import { indexFileEntryIfChanged } from "./sync-index.js";
import type { SyncProgressState } from "./sync-progress.js";
import { bumpSyncProgressTotal } from "./sync-progress.js";
import { deleteStaleIndexedPaths } from "./sync-stale.js";
const log = createSubsystemLogger("memory");
export async function syncMemoryFiles(params: {
workspaceDir: string;
extraPaths?: string[];
db: DatabaseSync;
needsFullReindex: boolean;
progress?: SyncProgressState;
batchEnabled: boolean;
concurrency: number;
runWithConcurrency: <T>(tasks: Array<() => Promise<T>>, concurrency: number) => Promise<T[]>;
indexFile: (entry: MemoryFileEntry) => Promise<void>;
vectorTable: string;
ftsTable: string;
ftsEnabled: boolean;
ftsAvailable: boolean;
model: string;
}) {
const files = await listMemoryFiles(params.workspaceDir, params.extraPaths);
const fileEntries = (
await Promise.all(files.map(async (file) => buildFileEntry(file, params.workspaceDir)))
).filter((entry): entry is MemoryFileEntry => entry !== null);
log.debug("memory sync: indexing memory files", {
files: fileEntries.length,
needsFullReindex: params.needsFullReindex,
batch: params.batchEnabled,
concurrency: params.concurrency,
});
const activePaths = new Set(fileEntries.map((entry) => entry.path));
bumpSyncProgressTotal(
params.progress,
fileEntries.length,
params.batchEnabled ? "Indexing memory files (batch)..." : "Indexing memory files…",
);
const tasks = fileEntries.map((entry) => async () => {
await indexFileEntryIfChanged({
db: params.db,
source: "memory",
needsFullReindex: params.needsFullReindex,
entry,
indexFile: params.indexFile,
progress: params.progress,
});
});
await params.runWithConcurrency(tasks, params.concurrency);
deleteStaleIndexedPaths({
db: params.db,
source: "memory",
activePaths,
vectorTable: params.vectorTable,
ftsTable: params.ftsTable,
ftsEnabled: params.ftsEnabled,
ftsAvailable: params.ftsAvailable,
model: params.model,
});
}

View File

@@ -1,38 +0,0 @@
export type SyncProgressState = {
completed: number;
total: number;
label?: string;
report: (update: { completed: number; total: number; label?: string }) => void;
};
export function bumpSyncProgressTotal(
progress: SyncProgressState | undefined,
delta: number,
label?: string,
) {
if (!progress) {
return;
}
progress.total += delta;
progress.report({
completed: progress.completed,
total: progress.total,
label,
});
}
export function bumpSyncProgressCompleted(
progress: SyncProgressState | undefined,
delta = 1,
label?: string,
) {
if (!progress) {
return;
}
progress.completed += delta;
progress.report({
completed: progress.completed,
total: progress.total,
label,
});
}

View File

@@ -1,81 +0,0 @@
import type { DatabaseSync } from "node:sqlite";
import { createSubsystemLogger } from "../logging/subsystem.js";
import type { SessionFileEntry } from "./session-files.js";
import {
buildSessionEntry,
listSessionFilesForAgent,
sessionPathForFile,
} from "./session-files.js";
import { indexFileEntryIfChanged } from "./sync-index.js";
import type { SyncProgressState } from "./sync-progress.js";
import { bumpSyncProgressCompleted, bumpSyncProgressTotal } from "./sync-progress.js";
import { deleteStaleIndexedPaths } from "./sync-stale.js";
const log = createSubsystemLogger("memory");
export async function syncSessionFiles(params: {
agentId: string;
db: DatabaseSync;
needsFullReindex: boolean;
progress?: SyncProgressState;
batchEnabled: boolean;
concurrency: number;
runWithConcurrency: <T>(tasks: Array<() => Promise<T>>, concurrency: number) => Promise<T[]>;
indexFile: (entry: SessionFileEntry) => Promise<void>;
vectorTable: string;
ftsTable: string;
ftsEnabled: boolean;
ftsAvailable: boolean;
model: string;
dirtyFiles: Set<string>;
}) {
const files = await listSessionFilesForAgent(params.agentId);
const activePaths = new Set(files.map((file) => sessionPathForFile(file)));
const indexAll = params.needsFullReindex || params.dirtyFiles.size === 0;
log.debug("memory sync: indexing session files", {
files: files.length,
indexAll,
dirtyFiles: params.dirtyFiles.size,
batch: params.batchEnabled,
concurrency: params.concurrency,
});
bumpSyncProgressTotal(
params.progress,
files.length,
params.batchEnabled ? "Indexing session files (batch)..." : "Indexing session files…",
);
const tasks = files.map((absPath) => async () => {
if (!indexAll && !params.dirtyFiles.has(absPath)) {
bumpSyncProgressCompleted(params.progress);
return;
}
const entry = await buildSessionEntry(absPath);
if (!entry) {
bumpSyncProgressCompleted(params.progress);
return;
}
await indexFileEntryIfChanged({
db: params.db,
source: "sessions",
needsFullReindex: params.needsFullReindex,
entry,
indexFile: params.indexFile,
progress: params.progress,
});
});
await params.runWithConcurrency(tasks, params.concurrency);
deleteStaleIndexedPaths({
db: params.db,
source: "sessions",
activePaths,
vectorTable: params.vectorTable,
ftsTable: params.ftsTable,
ftsEnabled: params.ftsEnabled,
ftsAvailable: params.ftsAvailable,
model: params.model,
});
}

View File

@@ -1,42 +0,0 @@
import type { DatabaseSync } from "node:sqlite";
export function deleteStaleIndexedPaths(params: {
db: DatabaseSync;
source: string;
activePaths: Set<string>;
vectorTable: string;
ftsTable: string;
ftsEnabled: boolean;
ftsAvailable: boolean;
model: string;
}) {
const staleRows = params.db
.prepare(`SELECT path FROM files WHERE source = ?`)
.all(params.source) as Array<{ path: string }>;
for (const stale of staleRows) {
if (params.activePaths.has(stale.path)) {
continue;
}
params.db
.prepare(`DELETE FROM files WHERE path = ? AND source = ?`)
.run(stale.path, params.source);
try {
params.db
.prepare(
`DELETE FROM ${params.vectorTable} WHERE id IN (SELECT id FROM chunks WHERE path = ? AND source = ?)`,
)
.run(stale.path, params.source);
} catch {}
params.db
.prepare(`DELETE FROM chunks WHERE path = ? AND source = ?`)
.run(stale.path, params.source);
if (params.ftsEnabled && params.ftsAvailable) {
try {
params.db
.prepare(`DELETE FROM ${params.ftsTable} WHERE path = ? AND source = ? AND model = ?`)
.run(stale.path, params.source, params.model);
} catch {}
}
}
}