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:
DottaandPaperclip committed 2026-10-29 08:23:30 -05:00
commit 867c00673b
579 files changed
+106238 -69119

No files matched your search

+11
View File
@@ -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";
+37
View File
@@ -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;
}
+8
View File
@@ -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,
+50
View File
@@ -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;
+2
View File
@@ -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",
+38
View File
@@ -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>;