Merge qualified backup expiration parent into GitHub connection work

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

* commit 'aae32f2f493848199f6cb8c0a9996af3ee0061b1': (30 commits)
  Scope status-card refresh tasks to their claim revision
  test(chat): retain completion identity before link metadata loads
  test(chat): locate completion through its rendered task link
  fix(chat): recover committed native Agent Chat replies (#18465)
  perf(heartbeat): drive retry promotion from durable deadlines (#18387)
  fix(ui): make the Coordinator composer footer opaque (#18455)
  fix(testops): verify unexpanded result job runs (#18462)
  test(server): observe restored Retry before draining unrelated work
  test(runner): own listener cleanup across setup failures
  test(runner): buffer upgrade frames until the wire subscribes
  fix(runner): preserve OpenCode written-answer defaults (#18456)
  fix(native): fence retries when replaying an audited Stop (#18457)
  test: await async outcomes and drive saved deadlines deterministically (#18416)
  fix(ui): retain startup autosave through session recovery (#18412)
  perf(server): recover independent native replies concurrently (#18421)
  perf(chat): scope task questions and batch inbox queue checks (#18422)
  fix(native): recover controllers across webserver replacement (#18343)
  fix(ui): restore task document authoring and add persistence journey (#18356)
  fix(tools): stabilize tied profile entry order (#18427)
  fix(adapter-utils): tolerate entries removed during workspace snapshots (#18426)
  ...
This commit is contained in:
DottaandPaperclip committed 2026-10-31 09:41:51 -05:00
commit f522ab07e2
484 files changed
+33834 -1620

No files matched your search

+26 -7
View File
@@ -39,7 +39,7 @@ import { requestRestorePointDeletion } from "./git-restore-point-ledger.js";
import { DELIVERY_QUEUES, notifyDeliveryWork } from "./delivery-work-notifications.js";
import { recordChatHandoff, recordAcceptedChatHandoff, lockAcceptedChatHandoffSource, recordChatCompletion, existingChatCompletionReply, acknowledgeChatCompletionReply } from "./chat-completion-delivery.js";
import { mirrorSlackBoardComment, slackBoardReplyBindings } from "./slack-board-messages.js";
import { assertAgentRunWriteAllowed } from "../agent-run-cancellation.js";
import { agentRunWritesRevoked, assertAgentRunWriteAllowed } from "../agent-run-cancellation.js";
import { assertLegacyRunTaskOwnership } from "../legacy-run-task-ownership.js";
import { retryIdempotentDatabaseOperation } from "../database-retry.js";
import { externalConversationStateSql, nonIdleSlackIssueCondition, resumeSlackConversation } from "./slack-conversation-state.js";
@@ -153,7 +153,7 @@ import {
isUuidLike,
normalizeIssueIdentifier as normalizeIssueReferenceIdentifier,
} from "@paperclipai/shared";
import { conflict, HttpError, notFound, unprocessable } from "../errors.js";
import { conflict, forbidden, HttpError, notFound, unprocessable } from "../errors.js";
import { isForeignKeyViolation } from "../db-errors.js";
import { logger } from "../middleware/logger.js";
import { parseObject } from "../adapters/utils.js";
@@ -1695,6 +1695,7 @@ function withAgentCommentAuthorizationMetadata(
const reason = authorizationReason?.trim() || "internal_agent_write";
return {
version: 1,
...(metadata?.chatMessage ? { chatMessage: metadata.chatMessage } : {}),
...(metadata?.sourceRunId !== undefined
? { sourceRunId: metadata.sourceRunId }
: {}),
@@ -8653,7 +8654,7 @@ export function issueService(db: Db) {
companyId: string,
issueId: string,
userId: string,
readAt: Date = new Date(),
readAt: Date | SQL = new Date(),
) => {
const now = new Date();
const [row] = await db
@@ -8672,7 +8673,7 @@ export function issueService(db: Db) {
issueReadStates.userId,
],
set: {
lastReadAt: readAt,
lastReadAt: sql`greatest(${issueReadStates.lastReadAt}, excluded.last_read_at)`,
updatedAt: now,
},
})
@@ -12937,6 +12938,7 @@ export function issueService(db: Db) {
sourceTrust?: typeof issueComments.$inferInsert.sourceTrust;
createdAt?: Date | string | null;
clientRequestId?: string;
proactiveChatMessage?: { sourceTaskId: string; userId: string };
/** Server-only: authenticated Paperclip messages also belong in the Slack thread. */
mirrorToSlack?: boolean;
},
@@ -12993,11 +12995,28 @@ export function issueService(db: Db) {
return existing;
}
}
if (options?.metadata?.chatMessage && !options.proactiveChatMessage) {
throw forbidden("Chat message provenance is server-owned");
}
if (issue.conversationAgentId && actor.agentId && issue.conversationAgentId !== actor.agentId) {
throw forbidden("Only this conversation's agent can post agent messages");
}
if (issue.conversationAgentId && actor.agentId && !actor.runId) {
throw forbidden("Agent chat messages require an authenticated run");
}
if (issue.conversationAgentId && actor.runId) {
const [run] = await dbOrTx.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, actor.runId));
const [current] = await dbOrTx.select().from(issues).where(eq(issues.id, issueId));
if (run?.status === "cancelled") throw conflict("This conversation turn was cancelled; it cannot post a reply");
if (run?.contextSnapshot?.conversationSessionGeneration !== current.conversationSessionGeneration) {
if (agentRunWritesRevoked(run)) throw conflict("This conversation turn was cancelled; it cannot post a reply");
const runTaskId = run?.nativeIssueId ?? run?.issueId ?? run?.contextSnapshot?.issueId ?? run?.contextSnapshot?.taskId;
if (options?.proactiveChatMessage) {
if (run?.status !== "running" || run.agentId !== actor.agentId || run.companyId !== issue.companyId
|| runTaskId !== options.proactiveChatMessage.sourceTaskId || runTaskId === issueId
|| current.conversationUserId !== options.proactiveChatMessage.userId
|| run.responsibleUserId !== current.conversationUserId) {
throw forbidden("Invalid background chat message authority");
}
} else if (runTaskId !== issueId || run?.contextSnapshot?.conversationSessionGeneration !== current.conversationSessionGeneration) {
throw conflict("Conversation session changed; this reply belongs to an earlier session");
}
}
@@ -13132,7 +13151,7 @@ export function issueService(db: Db) {
: (options?.metadata ?? null),
);
let comment: typeof issueComments.$inferSelect | null = null;
if (createdByRunId) {
if (createdByRunId && !options?.proactiveChatMessage) {
const existing = await dbOrTx
.select()
.from(issueComments)