Reconcile Claude protocol with merged runtime prerequisite

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* commit 'd67e3507b23041212fa26302ce6de078c5ebcc05':
  feat(runner): qualify Codex platform packages and cache hosted builds (#16028)
  fix(ui): default human task replies to the latest assigning agent (#15594)
  refactor(server): move provider setup, inventory and delivery into channel folders (#16058)
  fix(apps): show user avatars and center access rows (#15577)
  fix(issues): preserve human handoffs from the active agent run (#16082)

# Conflicts:
#	doc/plans/2026-10-06-default-agent-runner.md
#	maps/agent-access-map/runs-adapters.md
#	maps/feature-map/runs-adapters.md
#	packages/paperclip-runner/README.md
#	server/src/__tests__/dev-runner-native-binary.test.ts
This commit is contained in:
Dotta committed 2026-10-13 06:36:39 -05:00
commit abcd8261d2
58 files changed
+4036 -2462

No files matched your search

@@ -1286,7 +1286,7 @@ export class PaperclipRunnerToolAuthority {
agentId: this.binding.agentId, runId: this.binding.runId, issueId: child.id,
action: "issue.created", entityType: "issue", entityId: child.id,
details: { identifier: child.identifier, title: child.title, parentId: child.parentId,
assigneeAgentId: child.assigneeAgentId, status: childStatus, source: "paperclip_runner_protocol" },
assigneeAgentId: child.assigneeAgentId, assigneeUserId: child.assigneeUserId, status: childStatus, source: "paperclip_runner_protocol" },
});
publication = activity.publication;
}
@@ -1486,7 +1486,8 @@ export class PaperclipRunnerToolAuthority {
agentId: this.binding.agentId, runId: this.binding.runId, issueId: taskId,
action: "issue.reassigned", entityType: "issue", entityId: taskId,
details: { source: "paperclip_runner_protocol", reassignmentKey: durableKey, inputFingerprint: fingerprint,
previousAssigneeAgentId: target.assigneeAgentId, assigneeAgentId, reason, changed, receipt },
previousAssigneeAgentId: target.assigneeAgentId, previousAssigneeUserId: target.assigneeUserId,
assigneeAgentId, assigneeUserId, reason, changed, receipt },
});
publication = activity.publication;
return receipt;
@@ -1,7 +1,7 @@
import { randomUUID } from "node:crypto";
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import { eq, and } from "drizzle-orm";
import { activityLog, agents, companies, createDb, heartbeatRuns, issues, issueComments } from "@paperclipai/db";
import { activityLog, agents, authUsers, companyMemberships, companies, createDb, heartbeatRuns, issues, issueComments } from "@paperclipai/db";
import { startEmbeddedPostgresTestDatabase } from "../../__tests__/helpers/embedded-postgres.js";
import { issueService } from "../issues.js";
import { PaperclipRunnerToolAuthority } from "./paperclip-runner-tool-authority.js";
@@ -49,6 +49,24 @@ describe("runner task reassignment", () => {
expect(await db.select().from(issueComments).where(eq(issueComments.issueId, f.targetId))).toHaveLength(1);
});
it("records human assignment fields for native creation and reassignment", async () => {
const f = await fixture();
const person = randomUUID();
await db.insert(authUsers).values({ id: person, name: "Reviewer", email: `${person}@example.test`, createdAt: new Date(), updatedAt: new Date() });
await db.insert(companyMemberships).values({ companyId: f.companyId, principalType: "user", principalId: person, status: "active", membershipRole: "operator" });
await f.authority.execute({ ...f.call, arguments: { ...f.call.arguments, assigneeActorId: null, assigneeUserId: person } });
const [reassigned] = await db.select().from(activityLog).where(and(eq(activityLog.entityId, f.targetId), eq(activityLog.action, "issue.reassigned")));
expect(reassigned).toMatchObject({ actorType: "agent", actorId: f.agentId, details: {
previousAssigneeAgentId: f.agentId, previousAssigneeUserId: null,
assigneeAgentId: null, assigneeUserId: person, changed: true,
} });
const created = await f.authority.execute({ tool: "create_task", callId: "human-child", arguments: {
title: "Review result", assigneeUserId: person, idempotencyKey: "human-child",
} }) as { task: { id: string } };
const [creation] = await db.select().from(activityLog).where(and(eq(activityLog.entityId, created.task.id), eq(activityLog.action, "issue.created")));
expect(creation).toMatchObject({ actorType: "agent", actorId: f.agentId, details: { assigneeAgentId: null, assigneeUserId: person } });
});
it.each(["blocked", "backlog"])("preserves %s and does not wake work prematurely", async status => {
const f = await fixture(status);
await f.authority.execute(f.call);