mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-27 19:35:13 +01:00
fix(server): disclose the item cap on a resumed delta, and order the recovery outcomes
A resumed session that dropped delta messages still claimed a complete delta, and coverage.omittedMessageCount rode the full-list count into the delta payload. resumeDelta now carries its own omittedMessageCount, and the prompt states the delta is not complete when the cap drops a delta message. The recovery-outcomes select had no deterministic order, so the item cap kept an arbitrary 30 rows instead of the newest 30. Add createdAt and id order to that select, matching the pattern already used for issue thread interactions. Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
b48809485c
commit
0882a09eb3
5 files changed
+192
-30
No files matched your search
@@ -361,6 +361,7 @@ const support = await getEmbeddedPostgresTestSupport();
|
||||
agentId = randomUUID(),
|
||||
issueId = randomUUID(),
|
||||
baseRunId = randomUUID();
|
||||
let recoveryActionIds: string[] = [];
|
||||
beforeAll(async () => {
|
||||
database = await startEmbeddedPostgresTestDatabase(
|
||||
"paperclip-continuation-cap-lists-",
|
||||
@@ -458,9 +459,10 @@ const support = await getEmbeddedPostgresTestSupport();
|
||||
},
|
||||
})),
|
||||
);
|
||||
recoveryActionIds = Array.from({ length: 40 }, () => randomUUID());
|
||||
await db.insert(issueRecoveryActions).values(
|
||||
Array.from({ length: 40 }, (_, index) => ({
|
||||
id: randomUUID(),
|
||||
recoveryActionIds.map((id, index) => ({
|
||||
id,
|
||||
companyId,
|
||||
sourceIssueId: issueId,
|
||||
kind: "liveness",
|
||||
@@ -469,6 +471,7 @@ const support = await getEmbeddedPostgresTestSupport();
|
||||
fingerprint: `fp-${index}`,
|
||||
evidence: { executionReconciliation: { decision: "retry" } },
|
||||
nextAction: "none",
|
||||
createdAt: new Date(Date.UTC(2026, 8, 3, 0, index)),
|
||||
})),
|
||||
);
|
||||
}, 30_000);
|
||||
@@ -492,6 +495,34 @@ const support = await getEmbeddedPostgresTestSupport();
|
||||
expect(context.completedActions).toHaveLength(30);
|
||||
expect(context.recoveryOutcomes).toHaveLength(30);
|
||||
});
|
||||
it("reports the number of dropped delta messages on resumeDelta.omittedMessageCount", async () => {
|
||||
const context = await buildExecutionContinuation({
|
||||
db,
|
||||
companyId,
|
||||
issueId,
|
||||
agentId,
|
||||
previousContextRunId: baseRunId,
|
||||
context: {},
|
||||
summary: null,
|
||||
exposeLowTrustRaw: false,
|
||||
});
|
||||
expect(context.resumeDelta?.omittedMessageCount).toBe(10);
|
||||
});
|
||||
it("keeps the newest 30 recovery outcomes by createdAt", async () => {
|
||||
const context = await buildExecutionContinuation({
|
||||
db,
|
||||
companyId,
|
||||
issueId,
|
||||
agentId,
|
||||
previousContextRunId: baseRunId,
|
||||
context: {},
|
||||
summary: null,
|
||||
exposeLowTrustRaw: false,
|
||||
});
|
||||
expect(context.recoveryOutcomes?.map((row) => row.recoveryActionId)).toEqual(
|
||||
recoveryActionIds.slice(10),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -214,30 +214,38 @@ export async function buildExecutionContinuation(input: {
|
||||
const deliveredMessages = Array.isArray(priorEnvelope.messages)
|
||||
? priorEnvelope.messages.map(object)
|
||||
: null;
|
||||
const previousContextRunId = input.previousContextRunId ?? null;
|
||||
const deltaMessageCap =
|
||||
deliveredMessages && previousContextRunId
|
||||
? capMessagesKeepingOrigins(
|
||||
messages.filter(
|
||||
(message) =>
|
||||
originCommentIds.includes(message.id) ||
|
||||
!deliveredMessages.some(
|
||||
(prior) =>
|
||||
prior.id === message.id &&
|
||||
prior.updatedAt === message.updatedAt &&
|
||||
prior.body === message.body &&
|
||||
prior.deleted === message.deleted &&
|
||||
prior.authorId === message.authorId &&
|
||||
(prior.createdByRunId ?? null) ===
|
||||
message.createdByRunId &&
|
||||
JSON.stringify(prior.sourceTrust) ===
|
||||
JSON.stringify(message.sourceTrust),
|
||||
),
|
||||
),
|
||||
WAKE_CONTEXT_ITEM_CAP,
|
||||
originCommentIds,
|
||||
)
|
||||
: null;
|
||||
const resumeDelta =
|
||||
deliveredMessages && input.previousContextRunId
|
||||
deltaMessageCap && previousContextRunId
|
||||
? {
|
||||
baseRunId: input.previousContextRunId,
|
||||
messages: capMessagesKeepingOrigins(
|
||||
messages.filter(
|
||||
(message) =>
|
||||
originCommentIds.includes(message.id) ||
|
||||
!deliveredMessages.some(
|
||||
(prior) =>
|
||||
prior.id === message.id &&
|
||||
prior.updatedAt === message.updatedAt &&
|
||||
prior.body === message.body &&
|
||||
prior.deleted === message.deleted &&
|
||||
prior.authorId === message.authorId &&
|
||||
(prior.createdByRunId ?? null) ===
|
||||
message.createdByRunId &&
|
||||
JSON.stringify(prior.sourceTrust) ===
|
||||
JSON.stringify(message.sourceTrust),
|
||||
),
|
||||
),
|
||||
WAKE_CONTEXT_ITEM_CAP,
|
||||
originCommentIds,
|
||||
).kept,
|
||||
baseRunId: previousContextRunId,
|
||||
messages: deltaMessageCap.kept,
|
||||
...(deltaMessageCap.omitted > 0
|
||||
? { omittedMessageCount: deltaMessageCap.omitted }
|
||||
: {}),
|
||||
}
|
||||
: undefined;
|
||||
const latestRequest = messages.findLast(
|
||||
@@ -285,7 +293,8 @@ export async function buildExecutionContinuation(input: {
|
||||
eq(issueRecoveryActions.sourceIssueId, issueId),
|
||||
eq(issueRecoveryActions.status, "resolved"),
|
||||
),
|
||||
);
|
||||
)
|
||||
.orderBy(asc(issueRecoveryActions.createdAt), asc(issueRecoveryActions.id));
|
||||
const cappedMessages = capMessagesKeepingOrigins(
|
||||
messages,
|
||||
WAKE_CONTEXT_ITEM_CAP,
|
||||
|
||||
Reference in new issue
Block a user