mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-02 16:58:52 +01:00
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:
commit
f522ab07e2
484 files changed
+33834
-1620
No files matched your search
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user