mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-15 03:51:22 +02:00
feat: return completed handoffs to Agent Chat (#14408)
Return completed Agent Chat handoffs through a durable outbox and scope each generated update to its supplied tasks. Add recovery, browser delivery, result access, and calibrated quality coverage. Validated with two consecutive ten-case Claude/Codex campaigns, all CI checks, and a 5/5 review. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
3b4b270650
commit
83076d7e7c
59 files changed
+2696
-83
No files matched your search
@@ -18,6 +18,7 @@ import { ISSUE_DISPOSITION_REPAIR_RETRY_REASON } from "@paperclipai/shared";
|
||||
import { parseObject } from "../../../adapters/utils.js";
|
||||
import { evaluateAgentInvokabilityFromDb } from "../../../services/agent-invokability.js";
|
||||
import { budgetService } from "../../../services/budgets.js";
|
||||
import { isCompletedOnboardingHandoffWake } from "../../../services/chat-completion-delivery.js";
|
||||
import { isHeartbeatWakeOnDemandEnabled } from "../../../services/heartbeat-policy.js";
|
||||
import { collectDispositionRepairSourceState } from "../../../services/recovery/disposition-repair.js";
|
||||
import { legacyDispositionEpisode, legacyDispositionFingerprint } from "../../../services/recovery/legacy-continuation.js";
|
||||
@@ -608,6 +609,10 @@ export function createPostgresRunDispatchAdapter(
|
||||
isNonAssigneeWorkspaceBusyRetry: isNonAssigneeWorkspaceBusyRetry(retryReason, context),
|
||||
resumeIntent,
|
||||
wakeCommentIdPresent: Boolean(wakeCommentId),
|
||||
isCompletedOnboardingHandoffWake: await isCompletedOnboardingHandoffWake(dbOrTx, {
|
||||
companyId: input.companyId, issueId, agentId: input.agentId,
|
||||
reason: wakeReason, contextSnapshot: context,
|
||||
}),
|
||||
continuationParkApplies,
|
||||
continuationParksExecutor,
|
||||
continuationSummaryBody,
|
||||
|
||||
@@ -436,6 +436,28 @@ describe("decideQueuedRunStaleness", () => {
|
||||
expect(decideQueuedRunStaleness(facts, NOW)).toEqual({ stale: false });
|
||||
});
|
||||
|
||||
describe("completed onboarding handoff report", () => {
|
||||
const reportingFacts = (): QueuedRunFacts => ({
|
||||
...baseStalenessFacts(), issueStatus: "done", wakeReason: "issue_children_completed",
|
||||
isCompletedOnboardingHandoffWake: true,
|
||||
});
|
||||
|
||||
it("allows a verified child-completion report without reopening the parent", () => {
|
||||
expect(decideQueuedRunStaleness(reportingFacts(), NOW)).toEqual({ stale: false });
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ overrides: { isCompletedOnboardingHandoffWake: false }, errorCode: "issue_terminal_status" },
|
||||
{ overrides: { wakeReason: "issue_assigned" }, errorCode: "issue_terminal_status" },
|
||||
{ overrides: { issueStatus: "cancelled" }, errorCode: "issue_terminal_status" },
|
||||
{ overrides: { issueAssigneeAgentId: "agent-2" }, errorCode: "issue_assignee_changed" },
|
||||
{ overrides: { retryReasonKind: "max_turn_continuation" as const }, errorCode: "issue_not_in_progress" },
|
||||
])("preserves $errorCode guard with $overrides", ({ overrides, errorCode }) => {
|
||||
expect(decideQueuedRunStaleness({ ...reportingFacts(), ...overrides }, NOW))
|
||||
.toMatchObject({ stale: true, errorCode });
|
||||
});
|
||||
});
|
||||
|
||||
it("allows a non-assignee workspace-busy retry to bypass the ownership check", () => {
|
||||
const facts: QueuedRunFacts = {
|
||||
...baseStalenessFacts(),
|
||||
|
||||
@@ -151,6 +151,8 @@ export type QueuedRunFacts = {
|
||||
|
||||
resumeIntent: boolean;
|
||||
wakeCommentIdPresent: boolean;
|
||||
/** Verified from current company-scoped parent/child state immediately before dispatch. */
|
||||
isCompletedOnboardingHandoffWake?: boolean;
|
||||
|
||||
/** True when the run's wake or retry reason asks for a continuation the parked-summary check must inspect. */
|
||||
continuationParkApplies: boolean;
|
||||
@@ -609,7 +611,10 @@ export function decideQueuedRunStaleness(
|
||||
const statusOutcome = decideIssueStatus({
|
||||
status: facts.issueStatus,
|
||||
requiresInProgress,
|
||||
terminalBypass: facts.resumeIntent || facts.wakeCommentIdPresent,
|
||||
terminalBypass: facts.resumeIntent || facts.wakeCommentIdPresent || (
|
||||
facts.isCompletedOnboardingHandoffWake === true &&
|
||||
facts.wakeReason === "issue_children_completed" && facts.issueStatus === "done"
|
||||
),
|
||||
});
|
||||
if (statusOutcome === "terminal") {
|
||||
return {
|
||||
|
||||
Reference in new issue
Block a user