Address operator QoL review feedback
This commit is contained in:
@@ -845,6 +845,12 @@ describeEmbeddedPostgres("issueThreadInteractionService", () => {
|
|||||||
userId: "local-board",
|
userId: "local-board",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
expect(created).toMatchObject({
|
||||||
|
payload: {
|
||||||
|
supersedeOnUserComment: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const expired = await interactionsSvc.expireRequestConfirmationsSupersededByComment({
|
const expired = await interactionsSvc.expireRequestConfirmationsSupersededByComment({
|
||||||
id: issueId,
|
id: issueId,
|
||||||
companyId,
|
companyId,
|
||||||
@@ -903,6 +909,40 @@ describeEmbeddedPostgres("issueThreadInteractionService", () => {
|
|||||||
expect(rows[0]?.status).toBe("pending");
|
expect(rows[0]?.status).toBe("pending");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps legacy request confirmations pending when comment supersede was not stored", async () => {
|
||||||
|
const { companyId, issueId } = await seedConfirmationIssue("Legacy confirmation without comment supersede flag");
|
||||||
|
|
||||||
|
await db.insert(issueThreadInteractions).values({
|
||||||
|
id: randomUUID(),
|
||||||
|
companyId,
|
||||||
|
issueId,
|
||||||
|
kind: "request_confirmation",
|
||||||
|
status: "pending",
|
||||||
|
continuationPolicy: { kind: "none" },
|
||||||
|
payload: {
|
||||||
|
version: 1,
|
||||||
|
prompt: "Proceed with the current draft?",
|
||||||
|
},
|
||||||
|
createdByUserId: "local-board",
|
||||||
|
});
|
||||||
|
|
||||||
|
const expired = await interactionsSvc.expireRequestConfirmationsSupersededByComment({
|
||||||
|
id: issueId,
|
||||||
|
companyId,
|
||||||
|
}, {
|
||||||
|
id: randomUUID(),
|
||||||
|
createdAt: new Date(Date.now() + 1_000),
|
||||||
|
authorUserId: "local-board",
|
||||||
|
}, {
|
||||||
|
userId: "local-board",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(expired).toHaveLength(0);
|
||||||
|
const rows = await db.select().from(issueThreadInteractions);
|
||||||
|
expect(rows).toHaveLength(1);
|
||||||
|
expect(rows[0]?.status).toBe("pending");
|
||||||
|
});
|
||||||
|
|
||||||
it("does not supersede request confirmations for agent, system, or older user comments", async () => {
|
it("does not supersede request confirmations for agent, system, or older user comments", async () => {
|
||||||
const { companyId, issueId } = await seedConfirmationIssue("Comment supersede exclusions");
|
const { companyId, issueId } = await seedConfirmationIssue("Comment supersede exclusions");
|
||||||
|
|
||||||
|
|||||||
@@ -159,7 +159,18 @@ function shouldReturnAcceptedConfirmationToCreatorAgent(args: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function shouldSupersedeRequestConfirmationOnUserComment(interaction: RequestConfirmationInteraction) {
|
function shouldSupersedeRequestConfirmationOnUserComment(interaction: RequestConfirmationInteraction) {
|
||||||
return interaction.payload.supersedeOnUserComment !== false;
|
return interaction.payload.supersedeOnUserComment === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeCreateInteractionInput(input: CreateIssueThreadInteraction): CreateIssueThreadInteraction {
|
||||||
|
if (input.kind !== "request_confirmation") return input;
|
||||||
|
return {
|
||||||
|
...input,
|
||||||
|
payload: {
|
||||||
|
...input.payload,
|
||||||
|
supersedeOnUserComment: input.payload.supersedeOnUserComment ?? true,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function isCommentAtOrAfterInteraction(args: {
|
function isCommentAtOrAfterInteraction(args: {
|
||||||
@@ -673,7 +684,7 @@ export function issueThreadInteractionService(db: Db) {
|
|||||||
input: CreateIssueThreadInteraction,
|
input: CreateIssueThreadInteraction,
|
||||||
actor: InteractionActor,
|
actor: InteractionActor,
|
||||||
) => {
|
) => {
|
||||||
const data = createIssueThreadInteractionSchema.parse(input);
|
const data = normalizeCreateInteractionInput(createIssueThreadInteractionSchema.parse(input));
|
||||||
|
|
||||||
if (data.idempotencyKey) {
|
if (data.idempotencyKey) {
|
||||||
const existing = await getIdempotentInteraction({
|
const existing = await getIdempotentInteraction({
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// @vitest-environment jsdom
|
// @vitest-environment jsdom
|
||||||
|
|
||||||
import type { AnchorHTMLAttributes, ReactNode } from "react";
|
import { act, type AnchorHTMLAttributes, type ReactNode } from "react";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import type { Issue, RoutineListItem } from "@paperclipai/shared";
|
import type { Issue, RoutineListItem } from "@paperclipai/shared";
|
||||||
@@ -237,10 +237,6 @@ vi.mock("../components/AgentIconPicker", () => ({
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
|
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
|
||||||
|
|
||||||
async function act(callback: () => void | Promise<void>) {
|
|
||||||
await callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
function createRoutine(overrides: Partial<RoutineListItem>): RoutineListItem {
|
function createRoutine(overrides: Partial<RoutineListItem>): RoutineListItem {
|
||||||
return {
|
return {
|
||||||
id: "routine-1",
|
id: "routine-1",
|
||||||
|
|||||||
Reference in New Issue
Block a user