mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-11 21:59:38 +01:00
Merge master and reconcile skill preparation optimizations
Preserve installed-selection reuse and bundled relocation together with metadata-only package preparation. Retain both cleanup notifications, use the hardened upstream editor test, and regenerate unchanged skill SQL as migration 0414. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
commit
867c00673b
579 files changed
+106238
-69119
No files matched your search
@@ -166,6 +166,9 @@ export {
|
||||
} from "./responsible-user-denial.js";
|
||||
export type {
|
||||
AttentionDecisionVerb,
|
||||
DecisionHistoryItem,
|
||||
DecisionHistoryQuery,
|
||||
DecisionHistoryPage,
|
||||
AttentionDetailImage,
|
||||
AttentionFeed,
|
||||
AttentionItem,
|
||||
@@ -1128,6 +1131,8 @@ export type {
|
||||
IssueRelation,
|
||||
IssueRelationIssueSummary,
|
||||
IssueExecutionPolicy,
|
||||
RequiredQualityReview,
|
||||
QualityReviewBinding,
|
||||
IssueExecutionState,
|
||||
IssueExecutionStage,
|
||||
IssueExecutionStageParticipant,
|
||||
@@ -1165,6 +1170,9 @@ export type {
|
||||
AskUserQuestionsAnswer,
|
||||
AskUserQuestionsResult,
|
||||
RequestConfirmationIssueDocumentTarget,
|
||||
RequestConfirmationIssueAttachmentTarget,
|
||||
ConversationTaskHandoff,
|
||||
ConversationTaskHandoffPage,
|
||||
RequestConfirmationCustomTarget,
|
||||
RequestConfirmationTarget,
|
||||
RequestConfirmationPayload,
|
||||
@@ -1964,6 +1972,7 @@ export {
|
||||
updateIssueSchema,
|
||||
stalledReviewDecisionSchema,
|
||||
issueExecutionPolicySchema,
|
||||
requiredQualityReviewSchema,
|
||||
issueExecutionMonitorPolicySchema,
|
||||
issueExecutionStateSchema,
|
||||
resolveIssueRecoveryActionSchema,
|
||||
@@ -1999,6 +2008,7 @@ export {
|
||||
askUserQuestionsAnswerSchema,
|
||||
askUserQuestionsResultSchema,
|
||||
requestConfirmationIssueDocumentTargetSchema,
|
||||
requestConfirmationIssueAttachmentTargetSchema,
|
||||
requestConfirmationCustomTargetSchema,
|
||||
requestConfirmationTargetSchema,
|
||||
requestConfirmationPayloadSchema,
|
||||
@@ -2852,4 +2862,5 @@ export * from "./prompt-injection-prevention.js";
|
||||
|
||||
export * from "./workspace-provider.js";
|
||||
export * from "./workflows.js";
|
||||
export { updateWorkspacePreferenceSchema, type UpdateWorkspacePreference, type WorkspacePreference } from "./workspace-preferences.js";
|
||||
export * from "./agent-configuration.js";
|
||||
@@ -275,3 +275,40 @@ export interface AttentionFeed {
|
||||
countsBySourceKind: Record<AttentionSourceKind, number>;
|
||||
items: AttentionItem[];
|
||||
}
|
||||
/** Read-only projection of canonical task decisions; never a second write store. */
|
||||
export interface DecisionHistoryItem {
|
||||
id: string;
|
||||
sourceKind: "issue_thread_interaction" | "decision";
|
||||
sourceId: string;
|
||||
issueId: string;
|
||||
issueIdentifier: string | null;
|
||||
issueTitle: string;
|
||||
agentId: string | null;
|
||||
agentName: string | null;
|
||||
projectId: string | null;
|
||||
projectName: string | null;
|
||||
title: string;
|
||||
outcome: string;
|
||||
label: string;
|
||||
resolvedAt: string;
|
||||
resolvedByUserId: string | null;
|
||||
resolvedByAgentId: string | null;
|
||||
href: string;
|
||||
target: { kind: "issue_document"; issueId?: string; key: string; revisionId: string } | { kind: "issue_attachment"; attachmentId: string } | null;
|
||||
}
|
||||
|
||||
export interface DecisionHistoryQuery {
|
||||
cursor?: string;
|
||||
limit?: number;
|
||||
search?: string;
|
||||
outcome?: string;
|
||||
agentId?: string;
|
||||
projectId?: string;
|
||||
sort?: "newest" | "oldest";
|
||||
since?: string;
|
||||
}
|
||||
|
||||
export interface DecisionHistoryPage {
|
||||
items: DecisionHistoryItem[];
|
||||
nextCursor: string | null;
|
||||
}
|
||||
@@ -40,6 +40,9 @@ export type {
|
||||
} from "./summary-slot.js";
|
||||
export type {
|
||||
AttentionDecisionVerb,
|
||||
DecisionHistoryItem,
|
||||
DecisionHistoryQuery,
|
||||
DecisionHistoryPage,
|
||||
AttentionDetailImage,
|
||||
AttentionFeed,
|
||||
AttentionItem,
|
||||
@@ -730,6 +733,8 @@ export type {
|
||||
IssueExecutionMonitorPolicy,
|
||||
IssueExecutionMonitorState,
|
||||
IssueExecutionPolicy,
|
||||
RequiredQualityReview,
|
||||
QualityReviewBinding,
|
||||
IssueExecutionState,
|
||||
IssueExecutionStage,
|
||||
IssueExecutionStageParticipant,
|
||||
@@ -768,6 +773,9 @@ export type {
|
||||
AskUserQuestionsAnswer,
|
||||
AskUserQuestionsResult,
|
||||
RequestConfirmationIssueDocumentTarget,
|
||||
RequestConfirmationIssueAttachmentTarget,
|
||||
ConversationTaskHandoff,
|
||||
ConversationTaskHandoffPage,
|
||||
RequestConfirmationCustomTarget,
|
||||
RequestConfirmationTarget,
|
||||
RequestConfirmationPayload,
|
||||
|
||||
@@ -732,8 +732,21 @@ export interface IssueExecutionMonitorPolicy {
|
||||
recoveryPolicy?: IssueExecutionMonitorRecoveryPolicy | null;
|
||||
}
|
||||
|
||||
export interface RequiredQualityReview {
|
||||
version: 1;
|
||||
/** Named outputs whose exact versions require a human quality verdict. */
|
||||
deliverables: Array<{ key: string; label: string }>;
|
||||
}
|
||||
|
||||
export interface QualityReviewBinding {
|
||||
key: string;
|
||||
/** Server-stamped fingerprint; input values are never trusted. */
|
||||
requirementHash?: string;
|
||||
}
|
||||
|
||||
export interface IssueExecutionPolicy {
|
||||
mode: IssueExecutionPolicyMode;
|
||||
requiredQualityReview?: RequiredQualityReview;
|
||||
commentRequired: boolean;
|
||||
stages: IssueExecutionStage[];
|
||||
monitor?: IssueExecutionMonitorPolicy | null;
|
||||
@@ -1206,6 +1219,9 @@ export interface SuggestedTaskDraft {
|
||||
parentId?: string | null;
|
||||
title: string;
|
||||
description?: string | null;
|
||||
/** Persisted atomically as the new task's initial plan after acceptance. */
|
||||
initialPlan?: string | null;
|
||||
requiredQualityReview?: RequiredQualityReview;
|
||||
priority?: IssuePriority | null;
|
||||
workMode?: IssueWorkMode | null;
|
||||
assigneeAgentId?: string | null;
|
||||
@@ -1241,6 +1257,25 @@ export interface SuggestTasksResult {
|
||||
rejectionReason?: string | null;
|
||||
}
|
||||
|
||||
/** Server-recorded task creation in a standing chat's current session. */
|
||||
export interface ConversationTaskHandoff {
|
||||
id: string;
|
||||
issueId: string;
|
||||
identifier: string | null;
|
||||
title: string;
|
||||
status: IssueStatus;
|
||||
createdAt: string;
|
||||
/** Durable creation provenance; null for older or human-created handoffs. */
|
||||
sourceRunId: string | null;
|
||||
/** When present, the accepted suggestion already renders this task card. */
|
||||
suggestionInteractionId: string | null;
|
||||
}
|
||||
|
||||
export interface ConversationTaskHandoffPage {
|
||||
items: ConversationTaskHandoff[];
|
||||
nextCursor: string | null;
|
||||
}
|
||||
|
||||
export interface AskUserQuestionsQuestionOption {
|
||||
id: string;
|
||||
label: string;
|
||||
@@ -1365,8 +1400,20 @@ export interface RequestConfirmationCustomTarget {
|
||||
href?: string | null;
|
||||
}
|
||||
|
||||
/** An uploaded artifact's immutable bytes, independent of its display name. */
|
||||
export interface RequestConfirmationIssueAttachmentTarget {
|
||||
type: "issue_attachment";
|
||||
issueId?: string | null;
|
||||
attachmentId: string;
|
||||
assetId: string;
|
||||
sha256: string;
|
||||
label?: string | null;
|
||||
href?: string | null;
|
||||
}
|
||||
|
||||
export type RequestConfirmationTarget =
|
||||
| RequestConfirmationIssueDocumentTarget
|
||||
| RequestConfirmationIssueAttachmentTarget
|
||||
| RequestConfirmationCustomTarget;
|
||||
|
||||
/**
|
||||
@@ -1488,6 +1535,7 @@ export interface ConnectionIntentResult {
|
||||
}
|
||||
|
||||
export interface RequestConfirmationPayload {
|
||||
qualityReview?: QualityReviewBinding;
|
||||
version: 1;
|
||||
prompt: string;
|
||||
acceptLabel?: string | null;
|
||||
@@ -1531,6 +1579,8 @@ export interface RequestCheckboxConfirmationPayload {
|
||||
export type RequestItemVerdictValue = "approve" | "reject" | "defer";
|
||||
|
||||
export interface RequestItemVerdictsItem {
|
||||
qualityReview?: QualityReviewBinding;
|
||||
target?: RequestConfirmationTarget | null;
|
||||
id: string;
|
||||
label: string;
|
||||
description?: string | null;
|
||||
|
||||
@@ -440,6 +440,7 @@ export {
|
||||
updateIssueSchema,
|
||||
stalledReviewDecisionSchema,
|
||||
issueExecutionPolicySchema,
|
||||
requiredQualityReviewSchema,
|
||||
issueExecutionMonitorPolicySchema,
|
||||
issueExecutionStateSchema,
|
||||
issueRecoveryActionReadModelSchema,
|
||||
@@ -480,6 +481,7 @@ export {
|
||||
askUserQuestionsAnswerSchema,
|
||||
askUserQuestionsResultSchema,
|
||||
requestConfirmationIssueDocumentTargetSchema,
|
||||
requestConfirmationIssueAttachmentTargetSchema,
|
||||
requestConfirmationCustomTargetSchema,
|
||||
requestConfirmationTargetSchema,
|
||||
requestConfirmationPayloadSchema,
|
||||
|
||||
@@ -665,6 +665,12 @@ describe("issue validators", () => {
|
||||
expect(updateIssueSchema.safeParse(overrides).success).toBe(false);
|
||||
});
|
||||
|
||||
it.each(["PAPERCLIP_RUNNER_PREPARED_HARNESS_HOME", "paperclip_runner_prepared_harness_home"])("rejects task-level prepared storage via %s", key => {
|
||||
const overrides = { assigneeAdapterOverrides: { adapterConfig: { env: { [key]: "/home/agent/.claude", TASK_FLAG: "kept" } } } };
|
||||
expect(createIssueSchema.safeParse({ title: "Task", ...overrides }).success).toBe(false);
|
||||
expect(updateIssueSchema.safeParse(overrides).success).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects retired model profiles in agent runtime config", () => {
|
||||
const parsed = createAgentSchema.safeParse({
|
||||
name: "Coder",
|
||||
|
||||
@@ -308,6 +308,7 @@ export const issueAssigneeAdapterOverridesSchema = z
|
||||
.object({
|
||||
adapterConfig: z.record(z.string(), z.unknown())
|
||||
.refine(config => !Object.prototype.hasOwnProperty.call(config, "homeMode"), "homeMode must be set by an operator on the agent, not on a task")
|
||||
.refine(config => !config.env || typeof config.env !== "object" || !Object.keys(config.env).some(key => key.toUpperCase() === "PAPERCLIP_RUNNER_PREPARED_HARNESS_HOME"), "Prepared harness homes must be provisioned by the execution environment, not on a task")
|
||||
.optional(),
|
||||
useProjectWorkspace: z.boolean().optional(),
|
||||
})
|
||||
@@ -444,7 +445,19 @@ export const issueExecutionMonitorPolicySchema = z.object({
|
||||
.default(null),
|
||||
});
|
||||
|
||||
export const requiredQualityReviewSchema = z.object({
|
||||
version: z.literal(1),
|
||||
deliverables: z.array(z.object({
|
||||
key: z.string().regex(/^[a-z][a-z0-9_-]{0,79}$/),
|
||||
label: z.string().trim().min(1).max(120),
|
||||
}).strict()).min(1).max(10),
|
||||
}).strict().superRefine((value, ctx) => {
|
||||
if (new Set(value.deliverables.map(item => item.key)).size !== value.deliverables.length)
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Quality review deliverable keys must be unique", path: ["deliverables"] });
|
||||
});
|
||||
|
||||
export const issueExecutionPolicySchema = z.object({
|
||||
requiredQualityReview: requiredQualityReviewSchema.optional(),
|
||||
mode: z.enum(ISSUE_EXECUTION_POLICY_MODES).optional().default("normal"),
|
||||
commentRequired: z.boolean().optional().default(true),
|
||||
stages: z.array(issueExecutionStageSchema).default([]),
|
||||
@@ -1245,6 +1258,8 @@ export const suggestedTaskDraftSchema = z
|
||||
.pipe(z.string().trim().max(20000))
|
||||
.nullable()
|
||||
.optional(),
|
||||
initialPlan: z.string().trim().min(1).max(200000).nullable().optional(),
|
||||
requiredQualityReview: requiredQualityReviewSchema.optional(),
|
||||
priority: z.enum(ISSUE_PRIORITIES).nullable().optional(),
|
||||
workMode: z.enum(ISSUE_WORK_MODES).nullable().optional(),
|
||||
assigneeAgentId: z.string().guid().nullable().optional(),
|
||||
@@ -1580,8 +1595,18 @@ export const requestConfirmationCustomTargetSchema =
|
||||
revisionNumber: z.number().int().positive().nullable().optional(),
|
||||
});
|
||||
|
||||
export const requestConfirmationIssueAttachmentTargetSchema =
|
||||
requestConfirmationTargetBaseSchema.extend({
|
||||
type: z.literal("issue_attachment"),
|
||||
issueId: z.string().guid().nullable().optional(),
|
||||
attachmentId: z.string().guid(),
|
||||
assetId: z.string().guid(),
|
||||
sha256: z.string().regex(/^[a-f0-9]{64}$/, "sha256 must identify the exact attachment bytes"),
|
||||
});
|
||||
|
||||
export const requestConfirmationTargetSchema = z.discriminatedUnion("type", [
|
||||
requestConfirmationIssueDocumentTargetSchema,
|
||||
requestConfirmationIssueAttachmentTargetSchema,
|
||||
requestConfirmationCustomTargetSchema,
|
||||
]);
|
||||
|
||||
@@ -1613,7 +1638,13 @@ export const requestConfirmationSecretProposalPayloadSchema = z.object({
|
||||
expiresAt: z.string().datetime({ offset: true }),
|
||||
});
|
||||
|
||||
const qualityReviewBindingSchema = z.object({
|
||||
key: z.string().regex(/^[a-z][a-z0-9_-]{0,79}$/),
|
||||
requirementHash: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
||||
}).strict();
|
||||
|
||||
export const requestConfirmationPayloadSchema = z.object({
|
||||
qualityReview: qualityReviewBindingSchema.optional(),
|
||||
version: z.literal(1),
|
||||
prompt: z.string().trim().min(1).max(1000),
|
||||
acceptLabel: z.string().trim().min(1).max(80).nullable().optional(),
|
||||
@@ -1830,6 +1861,8 @@ export const requestItemVerdictValueSchema = z.enum([
|
||||
]);
|
||||
|
||||
export const requestItemVerdictsItemSchema = z.object({
|
||||
qualityReview: qualityReviewBindingSchema.optional(),
|
||||
target: requestConfirmationTargetSchema.nullable().optional(),
|
||||
id: z.string().trim().min(1).max(120),
|
||||
label: z.string().trim().min(1).max(120),
|
||||
description: z.string().trim().max(500).nullable().optional(),
|
||||
@@ -1865,7 +1898,12 @@ export const requestItemVerdictsPayloadSchema = z
|
||||
})
|
||||
.superRefine((value, ctx) => {
|
||||
const itemIds = new Set<string>();
|
||||
const qualityKeys = new Set<string>();
|
||||
for (const [index, item] of value.items.entries()) {
|
||||
if (item.qualityReview) {
|
||||
if (qualityKeys.has(item.qualityReview.key)) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Quality review keys must be unique within one request", path: ["items", index, "qualityReview", "key"] });
|
||||
qualityKeys.add(item.qualityReview.key);
|
||||
}
|
||||
if (itemIds.has(item.id)) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { z } from "zod";
|
||||
|
||||
/** Instance-personal presentation preference. Never changes company capabilities. */
|
||||
export interface WorkspacePreference {
|
||||
userId: string;
|
||||
coordinatorWorkspaceEnabled: boolean;
|
||||
updatedAt: string | null;
|
||||
}
|
||||
|
||||
export const updateWorkspacePreferenceSchema = z.object({
|
||||
coordinatorWorkspaceEnabled: z.boolean(),
|
||||
}).strict();
|
||||
|
||||
export type UpdateWorkspacePreference = z.infer<typeof updateWorkspacePreferenceSchema>;
|
||||
Reference in new issue
Block a user