bot comments

This commit is contained in:
Aadesh Kheria 2026-05-13 16:07:26 -07:00
parent 6b8838e623
commit 26f0ff67fc
9 changed files with 79 additions and 18 deletions

View File

@ -1,4 +1,4 @@
import { callReducerStrict, opt } from "@/lib/ai/spacetimedb-client";
import { callReducerStrict } from "@/lib/ai/spacetimedb-client";
import { assertIsAiChatReviewer } from "@/lib/ai/qa/reviewer-auth";
import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler";
import { adaptSchema, yupBoolean, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
@ -16,7 +16,7 @@ export const POST = createSmartRouteHandler({
question: yupString().defined(),
answer: yupString().defined(),
publish: yupBoolean().defined(),
requestId: yupString(),
requestId: yupString().defined(),
}).defined(),
method: yupString().oneOf(["POST"]).defined(),
}),
@ -38,7 +38,7 @@ export const POST = createSmartRouteHandler({
body.answer,
body.publish,
user.display_name ?? user.primary_email ?? user.id,
body.requestId
body.requestId,
]);
return {

View File

@ -34,7 +34,7 @@ export const POST = createSmartRouteHandler({
const token = getEnvVariable("STACK_MCP_LOG_TOKEN");
const reviewer = user.display_name ?? user.primary_email ?? user.id;
await callReducerStrict("upsert_qa_from_call", [
await callReducerStrict("upsert_qa_from_call_and_mark_reviewed", [
token,
body.correlationId,
body.correctedQuestion,
@ -42,7 +42,6 @@ export const POST = createSmartRouteHandler({
body.publish,
reviewer,
]);
await callReducerStrict("mark_human_reviewed", [token, body.correlationId, reviewer]);
return {
statusCode: 200,

View File

@ -27,7 +27,7 @@ const endpoints = [
},
{
path: "/api/latest/internal/mcp-review/add-manual",
validBody: { question: "q", answer: "a", publish: false },
validBody: { question: "q", answer: "a", publish: false, requestId: "test-req-id" },
invalidBody: { question: "q" },
},
{

View File

@ -77,7 +77,7 @@ describe.skipIf(!canRun)("private log tables and view gating", () => {
const seed = await niceBackendFetch("/api/latest/internal/mcp-review/add-manual", {
method: "POST",
accessType: "client",
body: { question: seedMarker, answer: "a", publish: false },
body: { question: seedMarker, answer: "a", publish: false, requestId: seedMarker },
});
expect(seed.status).toBe(200);

View File

@ -56,7 +56,7 @@ describe.skipIf(!canRun)("published_qa view projection", () => {
const publish = await niceBackendFetch("/api/latest/internal/mcp-review/add-manual", {
method: "POST",
accessType: "client",
body: { question: markerQuestion, answer: markerAnswer, publish: true },
body: { question: markerQuestion, answer: markerAnswer, publish: true, requestId: markerQuestion },
});
expect(publish.status).toBe(200);

View File

@ -40,7 +40,7 @@ describe.skipIf(!canRun)("published_qa visibility", () => {
const add = await niceBackendFetch("/api/latest/internal/mcp-review/add-manual", {
method: "POST",
accessType: "client",
body: { question: marker, answer: "x", publish: false },
body: { question: marker, answer: "x", publish: false, requestId: marker },
});
expect(add.status).toBe(200);
@ -63,7 +63,7 @@ describe.skipIf(!canRun)("published_qa visibility", () => {
const add = await niceBackendFetch("/api/latest/internal/mcp-review/add-manual", {
method: "POST",
accessType: "client",
body: { question: marker, answer: "x", publish: true },
body: { question: marker, answer: "x", publish: true, requestId: marker },
});
expect(add.status).toBe(200);
expect(await publishedQaContains(marker)).toBe(true);
@ -102,7 +102,7 @@ describe.skipIf(!canRun)("published_qa visibility", () => {
const add = await niceBackendFetch("/api/latest/internal/mcp-review/add-manual", {
method: "POST",
accessType: "client",
body: { question: marker, answer: "x", publish: true },
body: { question: marker, answer: "x", publish: true, requestId: marker },
});
expect(add.status).toBe(200);
expect(await publishedQaContains(marker)).toBe(true);
@ -137,7 +137,7 @@ describe.skipIf(!canRun)("published_qa visibility", () => {
const add = await niceBackendFetch("/api/latest/internal/mcp-review/add-manual", {
method: "POST",
accessType: "client",
body: { question: marker, answer: "x", publish: true },
body: { question: marker, answer: "x", publish: true, requestId: marker },
});
expect(add.status).toBe(200);
expect(await publishedQaContains(marker)).toBe(true);

View File

@ -61,7 +61,7 @@ describe.skipIf(!canRun)("qa_entries CRUD invariants", () => {
const add = await niceBackendFetch("/api/latest/internal/mcp-review/add-manual", {
method: "POST",
accessType: "client",
body: { question: marker, answer: "a", publish: true },
body: { question: marker, answer: "a", publish: true, requestId: marker },
});
expect(add.status).toBe(200);
@ -120,7 +120,7 @@ describe.skipIf(!canRun)("qa_entries CRUD invariants", () => {
const add = await niceBackendFetch("/api/latest/internal/mcp-review/add-manual", {
method: "POST",
accessType: "client",
body: { question: marker, answer: "a", publish: true },
body: { question: marker, answer: "a", publish: true, requestId: marker },
});
expect(add.status).toBe(200);

View File

@ -36,7 +36,7 @@ describe.skipIf(!canRun)("SpacetimeDB reducer auth", () => {
const seedPublish = await niceBackendFetch("/api/latest/internal/mcp-review/add-manual", {
method: "POST",
accessType: "client",
body: { question: seedMarker, answer: "a", publish: false },
body: { question: seedMarker, answer: "a", publish: false, requestId: seedMarker },
});
expect(seedPublish.status).toBe(200);
@ -65,7 +65,7 @@ describe.skipIf(!canRun)("SpacetimeDB reducer auth", () => {
name: "upsert_qa_from_call",
args: [wrong, "corr", "q", "a", false, "reviewer"],
},
{ name: "add_manual_qa", args: [wrong, "q", "a", false, "reviewer", opt(null)] },
{ name: "add_manual_qa", args: [wrong, "q", "a", false, "reviewer", "req-id"] },
{ name: "delete_qa_entry", args: [wrong, 0n] },
{ name: "update_qa_entry_with_publish", args: [wrong, 0n, "q", "a", false, "reviewer"] },
{

View File

@ -441,6 +441,68 @@ export const upsert_qa_from_call = spacetimedb.reducer(
}
);
export const upsert_qa_from_call_and_mark_reviewed = spacetimedb.reducer(
{
token: t.string(),
correlationId: t.string(),
question: t.string(),
answer: t.string(),
publish: t.bool(),
reviewer: t.string(),
},
(ctx, args) => {
if (args.token !== EXPECTED_LOG_TOKEN) {
throw new SenderError('Invalid log token');
}
const callLogRow = ctx.db.mcpCallLog.correlationId.find(args.correlationId);
if (callLogRow == null) {
throw new SenderError('Call log not found for correlationId: ' + args.correlationId);
}
let existing = null;
for (const row of ctx.db.qaEntries.shard.filter(0)) {
if (row.sourceMcpCorrelationId === args.correlationId) {
existing = row;
break;
}
}
if (existing != null) {
ctx.db.qaEntries.id.update({
...existing,
question: args.question,
answer: args.answer,
lastEditedBy: args.reviewer,
lastEditedAt: ctx.timestamp,
published: args.publish,
firstPublishedAt: args.publish ? (existing.firstPublishedAt ?? ctx.timestamp) : existing.firstPublishedAt,
lastPublishedAt: args.publish ? ctx.timestamp : existing.lastPublishedAt,
});
} else {
ctx.db.qaEntries.insert({
id: 0n,
shard: 0,
sourceMcpCorrelationId: args.correlationId,
requestId: undefined,
question: args.question,
answer: args.answer,
createdBy: args.reviewer,
createdAt: ctx.timestamp,
lastEditedBy: args.reviewer,
lastEditedAt: ctx.timestamp,
published: args.publish,
firstPublishedAt: args.publish ? ctx.timestamp : undefined,
lastPublishedAt: args.publish ? ctx.timestamp : undefined,
} as Parameters<typeof ctx.db.qaEntries.insert>[0]);
}
ctx.db.mcpCallLog.id.update({
...callLogRow,
humanReviewedAt: ctx.timestamp,
humanReviewedBy: args.reviewer,
});
}
);
export const add_manual_qa = spacetimedb.reducer(
{
token: t.string(),
@ -448,13 +510,13 @@ export const add_manual_qa = spacetimedb.reducer(
answer: t.string(),
publish: t.bool(),
createdBy: t.string(),
requestId: t.string().optional(),
requestId: t.string(),
},
(ctx, args) => {
if (args.token !== EXPECTED_LOG_TOKEN) {
throw new SenderError('Invalid log token');
}
if (args.requestId != null && args.requestId !== '') {
if (args.requestId !== '') {
for (const existing of ctx.db.qaEntries.iter()) {
if (existing.requestId === args.requestId) return;
}