mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-11 14:10:50 +02:00
fix(runner): accept native Rust question cancellation envelopes
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
d9e17db5ed
commit
75abb6a774
2 files changed
+25
-1
No files matched your search
@@ -318,6 +318,29 @@ describeEmbeddedPostgres("native question bridge", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("accepts the Rust ACPX cancellation payload without a redundant action field", async () => {
|
||||
await seed();
|
||||
const { interaction, cancelled } = await savedRuntimeQuestion();
|
||||
// Both Rust expire_runtime_requests and RuntimeRequestOutcome emit this
|
||||
// envelope. Cancellation authority is its committed event type.
|
||||
cancelled.payload = { provider: "acpx", requestId: "request-1", requestKind: "runtime", requestType: "input",
|
||||
turnId: cancelled.turnId, itemId: cancelled.itemId, reason: "session_interrupted", replayAllowed: false,
|
||||
adapter: "acpx-runtime-sidecar", request: { ...(runtimeRequestEvent().payload.request as Record<string, unknown>),
|
||||
turnId: cancelled.turnId, itemId: cancelled.itemId } };
|
||||
await projectNativeRuntimeRequest({ db, binding: binding(), event: cancelled });
|
||||
const [current] = await db.select().from(issueThreadInteractions).where(eq(issueThreadInteractions.id, interaction.id));
|
||||
expect(current).toMatchObject({ status: "expired", result: { cancelled: true, answers: [] } });
|
||||
});
|
||||
|
||||
it.each([{ action: "submit" }, { replayAllowed: true }, { response: { answers: {} } }])("rejects contradictory cancellation data %j", async (extra) => {
|
||||
await seed();
|
||||
const { interaction, cancelled } = await savedRuntimeQuestion();
|
||||
Object.assign(cancelled.payload, extra);
|
||||
await expect(projectNativeRuntimeRequest({ db, binding: binding(), event: cancelled })).rejects.toThrow("native_runtime_cancellation_invalid");
|
||||
const [current] = await db.select().from(issueThreadInteractions).where(eq(issueThreadInteractions.id, interaction.id));
|
||||
expect(current.status).toBe("pending");
|
||||
});
|
||||
|
||||
it("commits and acknowledges an ACP permission, then queues only an admin's exact turn-bound decision", async () => {
|
||||
await seed();
|
||||
const event = permissionRequestEvent();
|
||||
|
||||
@@ -131,7 +131,8 @@ export async function projectNativeRuntimeRequest(input: {
|
||||
// Permission cards have their own privileged resolver. A provider-loss
|
||||
// expiry is a durable handoff, not cancellation of a historical question.
|
||||
if (outcome.requestKind !== "runtime" || outcome.requestType !== "input") return null;
|
||||
if (outcome.action !== "cancel" || typeof outcome.requestId !== "string"
|
||||
if ((outcome.action !== undefined && outcome.action !== "cancel") || outcome.replayAllowed === true
|
||||
|| typeof outcome.requestId !== "string"
|
||||
|| !REQUEST_ID_PATTERN.test(outcome.requestId) || typeof input.event.turnId !== "string"
|
||||
|| outcome.turnId !== input.event.turnId || (outcome.itemId ?? null) !== (input.event.itemId ?? null)
|
||||
|| ["response", "answers", "answer", "replay"].some((key) => Object.hasOwn(outcome, key))) {
|
||||
|
||||
Reference in new issue
Block a user