Q143: earlier messages come from the database, page by page, back to the first one
Review limit (5) reached; the last finding (exhaustion latched) is fixed by taking it from each latest hasMore. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b18eb9ad14
commit
075dea725a
12 files changed
+213
-15
No files matched your search
@@ -72,6 +72,10 @@ export const platformContracts = {
|
||||
approvalDrifts: z.strictObject({ orgId: id }),
|
||||
runRequests: z.strictObject({ orgId: id, runId: id }),
|
||||
chatThreadHistory: z.strictObject({ orgId: id, threadId: id }),
|
||||
chatSpaceHistory: z.union([
|
||||
z.strictObject({ orgId: id, spaceId: id, beforeMessageId: id.nullable(), limit: z.number().int().min(1).max(500) }),
|
||||
z.strictObject({ orgId: id, spaceId: id, beforeMessageId: id.nullable(), fromMessageId: id }),
|
||||
]),
|
||||
search: z.strictObject({
|
||||
orgId: id, employeeId: id, query: z.string(), entityTypes: z.array(z.enum(SEARCH_ENTITY_TYPES)).optional(), limit: z.number().optional(),
|
||||
}) satisfies z.ZodType<C.SearchInput>,
|
||||
|
||||
@@ -28,7 +28,7 @@ import { SprintService } from "../services/sprint-service";
|
||||
import { ComposioConnectionService } from "../services/composio/composio-connection-service";
|
||||
import { ConnectorGrantsService } from "../services/composio/connector-grants";
|
||||
import { approvalDrifts } from "../services/approval-drift";
|
||||
import { threadHistory } from "../services/chat-history";
|
||||
import { spaceHistory, threadHistory } from "../services/chat-history";
|
||||
import { requestsForRun } from "../services/provider-request-store";
|
||||
import { setGoalAuditor } from "../services/goal-audit-inputs";
|
||||
import { IntegrationKeyStore } from "../services/integration-key-store";
|
||||
@@ -415,6 +415,7 @@ export function registerIpcHandlers(deps: IpcDeps): void {
|
||||
handle("grantConnector", (input) => mutate(() => new ConnectorGrantsService(deps.connection.db).grantFromBoard(input)));
|
||||
handle("runRequests", (input) => ok(requestsForRun(deps.connection.db, input.orgId, input.runId)));
|
||||
handle("chatThreadHistory", (input) => ok(threadHistory(deps.connection.db, input.orgId, input.threadId)));
|
||||
handle("chatSpaceHistory", (input) => ok(spaceHistory(deps.connection.db, input.orgId, input.spaceId, input.beforeMessageId, "limit" in input ? { limit: input.limit } : { fromMessageId: input.fromMessageId })));
|
||||
handle("approvalDrifts", (input) => {
|
||||
try {
|
||||
return ok(approvalDrifts(deps.connection.db, input.orgId));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import type { ThreadHistory } from "../../shared/chat-domain";
|
||||
import type { SpaceHistoryPage, ThreadHistory } from "../../shared/chat-domain";
|
||||
import { camel } from "./snapshot-service";
|
||||
|
||||
/** Every message of one thread with its attachments, mentions and commands, however far back it started. */
|
||||
@@ -14,3 +14,47 @@ export function threadHistory(db: DatabaseSync, orgId: string, threadId: string)
|
||||
slashInvocations: rows("select s.* from chat_slash_invocations s join chat_messages m on m.id = s.message_id where m.thread_id = ?"),
|
||||
};
|
||||
}
|
||||
|
||||
type Row = Record<string, unknown>;
|
||||
|
||||
/** Which older messages to return: the next page before the cursor, or every message from a loaded one up to the cursor. */
|
||||
export type HistoryScope = { limit: number } | { fromMessageId: string };
|
||||
|
||||
/** A conversation's messages before a loaded one (or its newest when none is loaded), in time then insertion order, with what they carry. */
|
||||
export function spaceHistory(db: DatabaseSync, orgId: string, spaceId: string, beforeMessageId: string | null, scope: HistoryScope): SpaceHistoryPage {
|
||||
if (!db.prepare("select id from chat_spaces where id = ? and org_id = ?").get(spaceId, orgId)) throw new Error("Chat space was not found.");
|
||||
const position = (messageId: string, what: string) => {
|
||||
const row = db.prepare("select created_at, rowid from chat_messages where id = ? and space_id = ? and org_id = ?")
|
||||
.get(messageId, spaceId, orgId) as { created_at: string; rowid: number } | undefined;
|
||||
if (!row) throw new Error(`The ${what} is not in this conversation.`);
|
||||
return [row.created_at, row.rowid] as const;
|
||||
};
|
||||
const conditions = ["space_id = ?"];
|
||||
const params: Array<string | number> = [spaceId];
|
||||
if (beforeMessageId !== null) {
|
||||
conditions.push("(created_at, rowid) < (?, ?)");
|
||||
params.push(...position(beforeMessageId, "earliest loaded message"));
|
||||
}
|
||||
if ("fromMessageId" in scope) {
|
||||
conditions.push("(created_at, rowid) >= (?, ?)");
|
||||
params.push(...position(scope.fromMessageId, "oldest loaded message"));
|
||||
}
|
||||
const limit = "limit" in scope ? scope.limit : -1;
|
||||
const rows = db.prepare(`select *, rowid as seq from chat_messages where ${conditions.join(" and ")} order by created_at desc, rowid desc limit ?`)
|
||||
.all(...params, limit === -1 ? -1 : limit + 1) as Row[];
|
||||
const page = (limit === -1 ? rows : rows.slice(0, limit)).reverse();
|
||||
const oldest = page[0];
|
||||
const hasMore = limit !== -1 ? rows.length > limit : oldest !== undefined && db.prepare(`
|
||||
select 1 from chat_messages where space_id = ? and (created_at, rowid) < (?, ?) limit 1
|
||||
`).get(spaceId, oldest.created_at as string, oldest.seq as number) !== undefined;
|
||||
const ids = JSON.stringify(page.map((row) => row.id));
|
||||
const carried = <T>(sql: string) => (db.prepare(sql).all(ids) as Row[]).map((row) => camel(row) as T);
|
||||
return {
|
||||
messages: page.map(({ seq: _seq, ...row }) => camel(row) as unknown as SpaceHistoryPage["messages"][number]),
|
||||
attachments: carried("select * from chat_attachments where message_id in (select value from json_each(?)) order by message_id"),
|
||||
mentions: carried("select * from chat_mentions where message_id in (select value from json_each(?)) order by message_id, offset_start"),
|
||||
slashInvocations: carried("select * from chat_slash_invocations where message_id in (select value from json_each(?)) order by message_id"),
|
||||
threads: carried("select * from chat_threads where id in (select thread_id from chat_messages where id in (select value from json_each(?)))"),
|
||||
hasMore,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user