mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-01 15:35:12 +01:00
fix(server): notify routine status after subtree restore
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
d288058ccd
commit
4b8d9a64ca
4 files changed
+28
-2
No files matched your search
@@ -9,7 +9,9 @@ scanned every five minutes, even with no routines or only completed history.
|
||||
`routine-run-status.ts` owns the projection, custody checks and repair transaction.
|
||||
The worker loads actual mismatches in keyset pages of 50 at startup. It accepts issue hints from issue
|
||||
creation, status/origin changes, checkout/release, routine issue association,
|
||||
execution control writes and review state writes. It uses the existing
|
||||
execution control writes, review state writes and subtree status restoration.
|
||||
Bulk restoration registers each issue hint in the same transaction before its
|
||||
grouped write. Restored history does not depend on an optional agent wake. It uses the existing
|
||||
transaction-aware work notification protocol. An outer transaction owns the hint;
|
||||
a savepoint release does not wake the repair worker before commit.
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ describeEmbeddedPostgres("issueTreeControlService", () => {
|
||||
beforeAll(async () => {
|
||||
if (externalUrl) {
|
||||
await applyPendingMigrations(externalUrl);
|
||||
db = createDb(externalUrl);
|
||||
db = createDb(externalUrl, { maxConnections: 6 });
|
||||
return;
|
||||
}
|
||||
tempDb = await startEmbeddedPostgresTestDatabase("paperclip-issue-tree-control-");
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { issueTreeControlService } from "../services/issue-tree-control.js";
|
||||
import { writeExecutionControl } from "../services/execution-control-notifications.js";
|
||||
import { DELIVERY_QUEUES } from "../services/delivery-work-notifications.js";
|
||||
import { randomUUID } from "node:crypto";
|
||||
@@ -174,6 +175,27 @@ const support = external ? { supported: true } : await getEmbeddedPostgresTestSu
|
||||
await db.transaction(async tx => { await notifyRoutineExecutionStatus(tx, otherRun.linkedIssueId!); });
|
||||
await projected(otherRun.id, "completed"); await quiet(); expect(errors).toEqual([]);
|
||||
});
|
||||
it("projects subtree cancel and restore without waking agents, then becomes quiet", async () => {
|
||||
const f = await fixture(), root = await dispatch(f), child = await dispatch(f);
|
||||
await issueService(db).update(child.linkedIssueId!, { parentId: root.linkedIssueId });
|
||||
const tree = issueTreeControlService(db), actor = { actorType: "system" as const, actorId: "routine-status-test" };
|
||||
await start(); await quiet();
|
||||
const cancel = await tree.createHold(f.companyId, root.linkedIssueId!, { mode: "cancel", actor });
|
||||
expect((await tree.cancelIssueStatusesForHold(f.companyId, root.linkedIssueId!, cancel.hold.id)).updatedIssueIds.sort())
|
||||
.toEqual([root.linkedIssueId, child.linkedIssueId].sort());
|
||||
await projected(root.id, "failed"); await projected(child.id, "failed"); await quiet();
|
||||
const restore = await tree.createHold(f.companyId, root.linkedIssueId!, { mode: "restore", actor });
|
||||
const restored = await tree.restoreIssueStatusesForHold(f.companyId, root.linkedIssueId!, restore.hold.id, { actor });
|
||||
expect(restored.updatedIssues).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ id: root.linkedIssueId, status: "todo" }),
|
||||
expect.objectContaining({ id: child.linkedIssueId, status: "todo" }),
|
||||
]));
|
||||
await projected(root.id, "issue_created"); await projected(child.id, "issue_created");
|
||||
expect(await saved(root.id)).toMatchObject({ completedAt: null, failureReason: null });
|
||||
expect(await saved(child.id)).toMatchObject({ completedAt: null, failureReason: null });
|
||||
expect(await db.select().from(heartbeatRuns)).toEqual([]);
|
||||
await quiet(); expect(errors).toEqual([]);
|
||||
});
|
||||
it("serializes a direct status hook with later task changes before projecting the routine", async () => {
|
||||
const f = await fixture(), run = await dispatch(f); await start(); await quiet();
|
||||
await db.update(issues).set({ status: "done" }).where(eq(issues.id, run.linkedIssueId!));
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { notifyRoutineExecutionStatus } from "./routine-execution-status-notifications.js";
|
||||
import { DELIVERY_QUEUES, notifyDeliveryWork } from "./delivery-work-notifications.js";
|
||||
import { notifyNativeStatusReassessment } from "./native-status-reassessment-notifications.js";
|
||||
import { notifyUnsafeWorkspaceExportRecovery } from "./unsafe-workspace-export-notifications.js";
|
||||
@@ -1006,6 +1007,7 @@ export function issueTreeControlService(db: Db) {
|
||||
for (const [status, issueIdsForStatus] of issueIdsByStatus) {
|
||||
if (issueIdsForStatus.length === 0) continue;
|
||||
for (const issueId of issueIdsForStatus) {
|
||||
await notifyRoutineExecutionStatus(tx, issueId, companyId);
|
||||
await notifyNativeStatusReassessment(tx, { companyId, issueId });
|
||||
await notifyTerminalWorkspaceWork(tx, `issue:${issueId}`);
|
||||
await notifyChatResponseAvailability(tx, { companyId, issueId });
|
||||
|
||||
Reference in new issue
Block a user