mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-10 20:46:28 +01:00
fix(idle): exclude completed feedback history from background work (#16764)
Allow private feedback history and acknowledged uploads to stop blocking idle sleep. Pending, failed, unfamiliar, and inconsistent delivery records remain blocked; preserve all other work and cleanup gates. Verified with 220 idle-safety tests, workspace typecheck/build, full CI, and Greptile Apex 5/5 on the reviewed head. Local full-suite limitations and isolated rechecks are documented in the PR. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
5dced3c766
commit
1558d9962c
3 files changed
+51
-1
No files matched your search
@@ -411,6 +411,14 @@ Rollback by setting the marker to `0` and restarting. No schema changes are
|
||||
required. This mechanism does not sleep claimed workspaces or replace a durable
|
||||
scheduler for their background work.
|
||||
|
||||
## Feedback history and idle sleep
|
||||
|
||||
Automatic idle checks treat private, unshared feedback as saved data. A sent
|
||||
feedback export is history only when its completion timestamp exists and its
|
||||
failure reason is clear. Pending, failed, unknown, and inconsistent exports
|
||||
still keep the instance awake. These checks do not delete history or change
|
||||
feedback sharing and retry behavior.
|
||||
|
||||
## Resource membership tables
|
||||
|
||||
Paperclip stores current-user sidebar membership state in:
|
||||
|
||||
@@ -5,6 +5,7 @@ import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest
|
||||
import {
|
||||
agentInstructionWorkingCopies, agents, agentApiKeys, agentWakeupRequests, companies, companySecretProposals, createDb,
|
||||
adapterAuthSessions, environments, environmentLeases, executionWorkspaces,
|
||||
feedbackExports, feedbackVotes,
|
||||
heartbeatRuns, issues, issueWatchdogs, issueThreadInteractions, issueQuestionResponseDeliveries,
|
||||
projects, projectWorkspaces, routines, routineTriggers, toolApplications, toolConnections, workspaceOperations, executionWorkspaceRuntimeLeases, type Db,
|
||||
} from "@paperclipai/db";
|
||||
@@ -186,6 +187,42 @@ if (!support.supported) console.warn(`Skipping idle sleep Postgres tests: ${supp
|
||||
return { companyId, agentId, runId };
|
||||
}
|
||||
|
||||
async function feedbackHistory(status: string, change: Partial<typeof feedbackExports.$inferInsert> = {}) {
|
||||
const f = await seed();
|
||||
const [issue] = await db.insert(issues).values({ companyId: f.companyId, title: "Finished fixture", status: "done" }).returning();
|
||||
const [vote] = await db.insert(feedbackVotes).values({ companyId: f.companyId, issueId: issue.id,
|
||||
targetType: "issue", targetId: issue.id, authorUserId: "fixture-user", vote: "up" }).returning();
|
||||
await db.insert(feedbackExports).values({ companyId: f.companyId, feedbackVoteId: vote.id,
|
||||
issueId: issue.id, authorUserId: "fixture-user", targetType: "issue", targetId: issue.id,
|
||||
vote: "up", status, targetSummary: {}, ...change });
|
||||
}
|
||||
|
||||
it("permits saved private feedback without sharing or deleting its history", async () => {
|
||||
await feedbackHistory("local_only");
|
||||
expect(await read()).toEqual(none);
|
||||
expect(await db.select({ status: feedbackExports.status }).from(feedbackExports)).toEqual([{ status: "local_only" }]);
|
||||
});
|
||||
|
||||
it("permits an acknowledged feedback upload and preserves its receipt", async () => {
|
||||
await feedbackHistory("sent", { exportedAt: new Date(now), failureReason: null });
|
||||
expect(await read()).toEqual(none);
|
||||
expect(await db.select({ status: feedbackExports.status, exportedAt: feedbackExports.exportedAt }).from(feedbackExports))
|
||||
.toEqual([{ status: "sent", exportedAt: new Date(now) }]);
|
||||
});
|
||||
|
||||
it.each(["pending", "failed", "unfamiliar"])("keeps %s feedback work awake even with an old completion stamp", async status => {
|
||||
await feedbackHistory(status, { exportedAt: new Date(now), failureReason: null });
|
||||
expect(await read()).toEqual(present);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ exportedAt: null, failureReason: null },
|
||||
{ exportedAt: new Date(now), failureReason: "unconfirmed delivery" },
|
||||
])("keeps inconsistent sent feedback awake: %j", async change => {
|
||||
await feedbackHistory("sent", change);
|
||||
expect(await read()).toEqual(present);
|
||||
});
|
||||
|
||||
async function scheduledRoutine(at = now + 600_000) {
|
||||
const fixture = await seed();
|
||||
const [routine] = await db.insert(routines).values({ companyId: fixture.companyId,
|
||||
|
||||
@@ -227,6 +227,11 @@ const WORK_CHECKS = [
|
||||
'connection-intent:' || d.interaction_id::text || ':rejected',
|
||||
'connection-intent:' || d.interaction_id::text || ':expired')
|
||||
AND (${finishedIdleWake('connection_wake')}) IS NOT TRUE)`,
|
||||
// Saved private feedback and acknowledged uploads have no delivery job.
|
||||
// Pending/failed exports and unfamiliar states retain the instance. A sent
|
||||
// label requires its completion stamp and a cleared failure reason.
|
||||
`SELECT 1 FROM feedback_exports WHERE (status = 'local_only'
|
||||
OR (status = 'sent' AND exported_at IS NOT NULL AND failure_reason IS NULL)) IS NOT TRUE`,
|
||||
// These less common work sources fail closed on any retained state. Their
|
||||
// terminal-state exceptions can be added with tests for the owning service.
|
||||
...[
|
||||
@@ -236,7 +241,7 @@ const WORK_CHECKS = [
|
||||
"execution_workspace_runtime_leases", "pipeline_automation_executions",
|
||||
"native_run_finalizations", "company_transfer_runs", "decisions", "decision_effect_executions",
|
||||
"decision_archive_notification_outbox", "browser_use_sessions", "browser_use_runs",
|
||||
"browser_use_browsers", "feedback_exports",
|
||||
"browser_use_browsers",
|
||||
"company_secret_proposals",
|
||||
"mcp_oauth_grants", "mcp_mutation_receipts", "mcp_event_subscriptions",
|
||||
"mcp_event_deliveries", "mcp_attachment_uploads", "dot_agent_bindings",
|
||||
|
||||
Reference in new issue
Block a user