Add feedback voting and thumbs capture flow

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
dotta 2026-04-02 09:11:49 -05:00
parent 3db6bdfc3c
commit c0d0d03bce
66 changed files with 18988 additions and 78 deletions

View file

@ -138,6 +138,16 @@ export {
export type {
Company,
FeedbackVote,
FeedbackDataSharingPreference,
FeedbackTargetType,
FeedbackVoteValue,
FeedbackTrace,
FeedbackTraceStatus,
FeedbackTraceTargetSummary,
FeedbackTraceBundleCaptureStatus,
FeedbackTraceBundleFile,
FeedbackTraceBundle,
CompanySkillSourceType,
CompanySkillTrustLevel,
CompanySkillCompatibility,
@ -325,6 +335,15 @@ export type {
ProviderQuotaResult,
} from "./types/index.js";
export {
DEFAULT_FEEDBACK_DATA_SHARING_PREFERENCE,
FEEDBACK_TARGET_TYPES,
FEEDBACK_DATA_SHARING_PREFERENCES,
FEEDBACK_TRACE_STATUSES,
FEEDBACK_VOTE_VALUES,
DEFAULT_FEEDBACK_DATA_SHARING_TERMS_VERSION,
} from "./types/feedback.js";
export {
instanceGeneralSettingsSchema,
patchInstanceGeneralSettingsSchema,
@ -338,9 +357,14 @@ export {
createCompanySchema,
updateCompanySchema,
updateCompanyBrandingSchema,
feedbackTargetTypeSchema,
feedbackTraceStatusSchema,
feedbackVoteValueSchema,
upsertIssueFeedbackVoteSchema,
type CreateCompany,
type UpdateCompany,
type UpdateCompanyBranding,
type UpsertIssueFeedbackVote,
agentSkillStateSchema,
agentSkillSyncModeSchema,
agentSkillEntrySchema,

View file

@ -31,6 +31,10 @@ export interface CompanyPortabilityCompanyManifestEntry {
brandColor: string | null;
logoPath: string | null;
requireBoardApprovalForNewAgents: boolean;
feedbackDataSharingEnabled: boolean;
feedbackDataSharingConsentAt: string | null;
feedbackDataSharingConsentByUserId: string | null;
feedbackDataSharingTermsVersion: string | null;
}
export interface CompanyPortabilitySidebarOrder {

View file

@ -12,6 +12,10 @@ export interface Company {
budgetMonthlyCents: number;
spentMonthlyCents: number;
requireBoardApprovalForNewAgents: boolean;
feedbackDataSharingEnabled: boolean;
feedbackDataSharingConsentAt: Date | null;
feedbackDataSharingConsentByUserId: string | null;
feedbackDataSharingTermsVersion: string | null;
brandColor: string | null;
logoAssetId: string | null;
logoUrl: string | null;

View file

@ -0,0 +1,120 @@
export const FEEDBACK_TARGET_TYPES = ["issue_comment", "issue_document_revision"] as const;
export type FeedbackTargetType = (typeof FEEDBACK_TARGET_TYPES)[number];
export const FEEDBACK_VOTE_VALUES = ["up", "down"] as const;
export type FeedbackVoteValue = (typeof FEEDBACK_VOTE_VALUES)[number];
export const FEEDBACK_DATA_SHARING_PREFERENCES = ["allowed", "not_allowed", "prompt"] as const;
export type FeedbackDataSharingPreference = (typeof FEEDBACK_DATA_SHARING_PREFERENCES)[number];
export const DEFAULT_FEEDBACK_DATA_SHARING_PREFERENCE: FeedbackDataSharingPreference = "prompt";
export const FEEDBACK_TRACE_STATUSES = ["local_only", "pending", "sent", "failed"] as const;
export type FeedbackTraceStatus = (typeof FEEDBACK_TRACE_STATUSES)[number];
export const DEFAULT_FEEDBACK_DATA_SHARING_TERMS_VERSION = "feedback-data-sharing-v1";
export interface FeedbackVote {
id: string;
companyId: string;
issueId: string;
targetType: FeedbackTargetType;
targetId: string;
authorUserId: string;
vote: FeedbackVoteValue;
reason: string | null;
sharedWithLabs: boolean;
sharedAt: Date | null;
consentVersion: string | null;
redactionSummary: Record<string, unknown> | null;
createdAt: Date;
updatedAt: Date;
}
export interface FeedbackTraceTargetSummary {
label: string;
excerpt: string | null;
authorAgentId: string | null;
authorUserId: string | null;
createdAt: Date | null;
documentKey: string | null;
documentTitle: string | null;
revisionNumber: number | null;
}
export interface FeedbackTrace {
id: string;
companyId: string;
feedbackVoteId: string;
issueId: string;
projectId: string | null;
issueIdentifier: string | null;
issueTitle: string;
authorUserId: string;
targetType: FeedbackTargetType;
targetId: string;
vote: FeedbackVoteValue;
status: FeedbackTraceStatus;
destination: string | null;
exportId: string | null;
consentVersion: string | null;
schemaVersion: string;
bundleVersion: string;
payloadVersion: string;
payloadDigest: string | null;
payloadSnapshot: Record<string, unknown> | null;
targetSummary: FeedbackTraceTargetSummary;
redactionSummary: Record<string, unknown> | null;
attemptCount: number;
lastAttemptedAt: Date | null;
exportedAt: Date | null;
failureReason: string | null;
createdAt: Date;
updatedAt: Date;
}
export type FeedbackTraceBundleCaptureStatus = "full" | "partial" | "unavailable";
export interface FeedbackTraceBundleFile {
path: string;
contentType: string;
encoding: "utf8";
byteLength: number;
sha256: string;
source:
| "paperclip_run"
| "paperclip_run_events"
| "paperclip_run_log"
| "codex_session"
| "claude_stream_json"
| "claude_project_session"
| "claude_project_artifact"
| "claude_debug_log"
| "claude_task_metadata"
| "opencode_session"
| "opencode_session_diff"
| "opencode_message"
| "opencode_message_part"
| "opencode_project"
| "opencode_todo";
contents: string;
}
export interface FeedbackTraceBundle {
traceId: string;
exportId: string | null;
companyId: string;
issueId: string;
issueIdentifier: string | null;
adapterType: string | null;
captureStatus: FeedbackTraceBundleCaptureStatus;
notes: string[];
envelope: Record<string, unknown>;
surface: Record<string, unknown> | null;
paperclipRun: Record<string, unknown> | null;
rawAdapterTrace: Record<string, unknown> | null;
normalizedAdapterTrace: Record<string, unknown> | null;
privacy: Record<string, unknown> | null;
integrity: Record<string, unknown>;
files: FeedbackTraceBundleFile[];
}

View file

@ -1,4 +1,16 @@
export type { Company } from "./company.js";
export type {
FeedbackVote,
FeedbackDataSharingPreference,
FeedbackTargetType,
FeedbackVoteValue,
FeedbackTrace,
FeedbackTraceStatus,
FeedbackTraceTargetSummary,
FeedbackTraceBundleCaptureStatus,
FeedbackTraceBundleFile,
FeedbackTraceBundle,
} from "./feedback.js";
export type { InstanceExperimentalSettings, InstanceGeneralSettings, InstanceSettings } from "./instance.js";
export type {
CompanySkillSourceType,

View file

@ -1,5 +1,8 @@
import type { FeedbackDataSharingPreference } from "./feedback.js";
export interface InstanceGeneralSettings {
censorUsernameInLogs: boolean;
feedbackDataSharingPreference: FeedbackDataSharingPreference;
}
export interface InstanceExperimentalSettings {

View file

@ -36,6 +36,10 @@ export const portabilityCompanyManifestEntrySchema = z.object({
brandColor: z.string().nullable(),
logoPath: z.string().nullable(),
requireBoardApprovalForNewAgents: z.boolean(),
feedbackDataSharingEnabled: z.boolean().default(false),
feedbackDataSharingConsentAt: z.string().datetime().nullable().default(null),
feedbackDataSharingConsentByUserId: z.string().nullable().default(null),
feedbackDataSharingTermsVersion: z.string().nullable().default(null),
});
export const portabilitySidebarOrderSchema = z.object({

View file

@ -3,6 +3,7 @@ import { COMPANY_STATUSES } from "../constants.js";
const logoAssetIdSchema = z.string().uuid().nullable().optional();
const brandColorSchema = z.string().regex(/^#[0-9a-fA-F]{6}$/).nullable().optional();
const feedbackDataSharingTermsVersionSchema = z.string().min(1).nullable().optional();
export const createCompanySchema = z.object({
name: z.string().min(1),
@ -18,6 +19,10 @@ export const updateCompanySchema = createCompanySchema
status: z.enum(COMPANY_STATUSES).optional(),
spentMonthlyCents: z.number().int().nonnegative().optional(),
requireBoardApprovalForNewAgents: z.boolean().optional(),
feedbackDataSharingEnabled: z.boolean().optional(),
feedbackDataSharingConsentAt: z.coerce.date().nullable().optional(),
feedbackDataSharingConsentByUserId: z.string().min(1).nullable().optional(),
feedbackDataSharingTermsVersion: feedbackDataSharingTermsVersionSchema,
brandColor: brandColorSchema,
logoAssetId: logoAssetIdSchema,
});

View file

@ -0,0 +1,22 @@
import { z } from "zod";
import {
FEEDBACK_DATA_SHARING_PREFERENCES,
FEEDBACK_TARGET_TYPES,
FEEDBACK_TRACE_STATUSES,
FEEDBACK_VOTE_VALUES,
} from "../types/feedback.js";
export const feedbackTargetTypeSchema = z.enum(FEEDBACK_TARGET_TYPES);
export const feedbackTraceStatusSchema = z.enum(FEEDBACK_TRACE_STATUSES);
export const feedbackVoteValueSchema = z.enum(FEEDBACK_VOTE_VALUES);
export const feedbackDataSharingPreferenceSchema = z.enum(FEEDBACK_DATA_SHARING_PREFERENCES);
export const upsertIssueFeedbackVoteSchema = z.object({
targetType: feedbackTargetTypeSchema,
targetId: z.string().uuid(),
vote: feedbackVoteValueSchema,
reason: z.string().trim().max(1000).optional(),
allowSharing: z.boolean().optional(),
});
export type UpsertIssueFeedbackVote = z.infer<typeof upsertIssueFeedbackVoteSchema>;

View file

@ -24,6 +24,14 @@ export {
type UpdateCompany,
type UpdateCompanyBranding,
} from "./company.js";
export {
feedbackDataSharingPreferenceSchema,
feedbackTargetTypeSchema,
feedbackTraceStatusSchema,
feedbackVoteValueSchema,
upsertIssueFeedbackVoteSchema,
type UpsertIssueFeedbackVote,
} from "./feedback.js";
export {
companySkillSourceTypeSchema,
companySkillTrustLevelSchema,

View file

@ -1,7 +1,12 @@
import { z } from "zod";
import { DEFAULT_FEEDBACK_DATA_SHARING_PREFERENCE } from "../types/feedback.js";
import { feedbackDataSharingPreferenceSchema } from "./feedback.js";
export const instanceGeneralSettingsSchema = z.object({
censorUsernameInLogs: z.boolean().default(false),
feedbackDataSharingPreference: feedbackDataSharingPreferenceSchema.default(
DEFAULT_FEEDBACK_DATA_SHARING_PREFERENCE,
),
}).strict();
export const patchInstanceGeneralSettingsSchema = instanceGeneralSettingsSchema.partial();