Files
PaperClipAI/packages/shared/src/validators/instance.ts
T
DottaandPaperclip b400f76647 feat(ui): submit task composers with Enter by default (#19272)
Use Enter to submit task replies and new tasks, with Shift+Enter for a new line. Add an experimental preference to require Command/Ctrl+Enter.

Preserve autocomplete selection and IME confirmation, and retain the modifier shortcut until saved settings load.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-11-05 11:40:18 -06:00

168 lines
7.9 KiB
TypeScript

import { z } from "zod";
import { DEFAULT_FEEDBACK_DATA_SHARING_PREFERENCE } from "../types/feedback.js";
import {
DAILY_RETENTION_PRESETS,
WEEKLY_RETENTION_PRESETS,
MONTHLY_RETENTION_PRESETS,
DEFAULT_BACKUP_RETENTION,
} from "../types/instance.js";
import { feedbackDataSharingPreferenceSchema } from "./feedback.js";
import { shapeWithoutDefaults } from "./partial.js";
function presetSchema<T extends readonly number[]>(presets: T, label: string) {
return z.number().refine(
(v): v is T[number] => (presets as readonly number[]).includes(v),
{ message: `${label} must be one of: ${presets.join(", ")}` },
);
}
export const backupRetentionPolicySchema = z.object({
dailyDays: presetSchema(DAILY_RETENTION_PRESETS, "dailyDays").default(DEFAULT_BACKUP_RETENTION.dailyDays),
weeklyWeeks: presetSchema(WEEKLY_RETENTION_PRESETS, "weeklyWeeks").default(DEFAULT_BACKUP_RETENTION.weeklyWeeks),
monthlyMonths: presetSchema(MONTHLY_RETENTION_PRESETS, "monthlyMonths").default(DEFAULT_BACKUP_RETENTION.monthlyMonths),
});
export const instanceGeneralSettingsSchema = z.object({
censorUsernameInLogs: z.boolean().default(false),
feedbackDataSharingPreference: feedbackDataSharingPreferenceSchema.default(
DEFAULT_FEEDBACK_DATA_SHARING_PREFERENCE,
),
backupRetention: backupRetentionPolicySchema.default(DEFAULT_BACKUP_RETENTION),
// Execution policy. Absent/"any" = unrestricted; "kubernetes" forces the
// Kubernetes sandbox provider and denies local/ssh execution (cloud_tenant).
executionMode: z.enum(["kubernetes", "any"]).optional(),
}).strict();
export const patchInstanceGeneralSettingsSchema = z
.object(shapeWithoutDefaults(instanceGeneralSettingsSchema.shape))
.partial()
.strict();
export const instanceExperimentalSettingsSchema = z.object({
enableEnvironments: z.boolean().default(false),
enableNativeRunner: z.boolean().default(true),
enableAiConnectionRouters: z.boolean().default(false),
enableManagedSandboxOnly: z.boolean().default(false),
enableIsolatedWorkspaces: z.boolean().default(false),
remoteWorkspaceStorageEnabled: z.boolean().default(true),
enableIsolatedWorkspacesByDefault: z.boolean().default(false),
enableStreamlinedLeftNavigation: z.boolean().default(true),
enableStreamlinedUi: z.boolean().default(true),
// Deprecated compatibility key. Apps is a standard product surface and is
// always enabled; this remains accepted so older stored rows and managed
// configs continue to load during upgrades.
enableApps: z.boolean().default(true),
enablePublicMcp: z.boolean().default(false),
enableOpenAiDot: z.boolean().default(false),
enableGrokBot: z.boolean().default(false),
enableMuse: z.boolean().default(false),
enableChatConnectors: z.boolean().default(false),
enableGitHubReviewBots: z.boolean().default(false),
// Compatibility only: old stored and managed values must still parse.
enableMcpAggregators: z.boolean().default(true),
enableMemoryConnectors: z.boolean().default(false),
enablePaperclipNotebook: z.boolean().default(false),
enableGoogleWorkspaceConnector: z.boolean().default(false),
enableCases: z.boolean().default(false),
enableAgentChat: z.boolean().default(false),
enableCombinedInboxTasks: z.boolean().default(false),
enableArtifactsV1: z.boolean().default(true),
enableConferenceRoomChat: z.boolean().default(false),
enableClassicTaskInterface: z.boolean().default(false),
enableLegacyComposerSubmit: z.boolean().default(false),
enableIssuePlanDecompositions: z.boolean().default(false),
enableExperimentalFileViewer: z.boolean().default(false),
enableExternalObjects: z.boolean().default(false),
enableSmokeLab: z.boolean().default(false),
enableBuiltInAgents: z.boolean().default(false),
enableBetaSkills: z.boolean().default(false),
enableSummaries: z.boolean().default(false),
enableStatusCards: z.boolean().default(false),
enableDecisions: z.boolean().default(false),
enableFastResponses: z.boolean().default(false),
enableGoalsSidebarLink: z.boolean().default(false),
enableServerInfoDebugView: z.boolean().default(false),
enablePaperclipDeveloperMode: z.boolean().default(false),
enableSimplifiedEnglishInteractions: z.boolean().default(false),
enableFirstTaskPlanProposal: z.boolean().default(false),
autoRestartDevServerWhenIdle: z.boolean().default(false),
enableWorkspaceBranchReconcileForward: z.boolean().default(true),
enableWorkspaceDirtyQuarantineRepair: z.boolean().default(true),
enableOwnerInstanceAdmin: z.boolean().default(false),
// Kill switch for the sandbox duplex command-stream bridge. Default off. When
// off the host keeps the file bridge for every run with no manifest change and
// no redeploy. The host reads this per run before it selects the transport.
enableSandboxDuplexBridge: z.boolean().default(false),
// Deprecated compatibility key. Runner ingress follows enableNativeRunner;
// this remains accepted so older stored rows and managed configs keep loading.
enableRunnerPreviewIngress: z.boolean().default(false),
enableWorktreeRunExecution: z.boolean().default(false),
worktreeRunExecutionActivatedAt: z.string().datetime().nullable().default(null),
worktreeRunExecutionActivationInstanceId: z.string().min(1).nullable().default(null),
}).strict();
export const patchInstanceExperimentalSettingsSchema = z
.object(
shapeWithoutDefaults(
instanceExperimentalSettingsSchema
.omit({
worktreeRunExecutionActivatedAt: true,
worktreeRunExecutionActivationInstanceId: true,
})
.shape,
),
)
.partial()
.strip();
export const managedSettingMetadataSchema = z.object({
managed: z.literal(true),
managedBy: z.literal("paperclip-cloud"),
}).strict();
// Response shape of the experimental settings endpoints: on cloud-managed
// instances every overlaid key is listed in `managedKeys`; self-hosted
// responses omit the field entirely.
export const instanceExperimentalSettingsWithManagedSchema = instanceExperimentalSettingsSchema.extend({
managedKeys: z.record(z.string(), managedSettingMetadataSchema).optional(),
}).strict();
export const patchInstanceSettingsSchema = z.object({
defaultEnvironmentId: z.string().guid().nullable().optional(),
}).strict();
// The longest time a task drain can run before it expires on its own. A
// caller can send a shorter `ttlMs`, but not a longer one — the request must
// fail instead of the server silently clamping the value.
export const MAX_TASK_DRAIN_TTL_MS = 24 * 60 * 60 * 1000;
export const startTaskDrainRequestSchema = z.object({
purpose: z.literal("idle").optional(),
ttlMs: z.number().int().positive().max(MAX_TASK_DRAIN_TTL_MS).nullable().optional(),
}).strict().refine(value => value.purpose !== "idle" ||
(typeof value.ttlMs === "number" && value.ttlMs >= 5_000 && value.ttlMs <= 300_000),
{ message: "Idle holds require ttlMs between 5000 and 300000", path: ["ttlMs"] });
export type InstanceGeneralSettings = z.infer<typeof instanceGeneralSettingsSchema>;
// The patch schema removes each default so an absent key stays absent. Declare
// the type from the full settings type, so every field keeps its precise type.
export type PatchInstanceGeneralSettings = Partial<InstanceGeneralSettings>;
export type InstanceExperimentalSettings = z.infer<typeof instanceExperimentalSettingsSchema>;
export type PatchInstanceExperimentalSettings = Partial<
Omit<
InstanceExperimentalSettings,
"worktreeRunExecutionActivatedAt" | "worktreeRunExecutionActivationInstanceId"
>
>;
export type PatchInstanceSettings = z.infer<typeof patchInstanceSettingsSchema>;
export type StartTaskDrainRequest = z.infer<typeof startTaskDrainRequestSchema>;
export const instanceSettingsSchema = z.object({
id: z.string().guid(),
defaultEnvironmentId: z.string().guid().nullable(),
general: instanceGeneralSettingsSchema,
experimental: instanceExperimentalSettingsWithManagedSchema,
createdAt: z.union([z.date(), z.string().datetime()]),
updatedAt: z.union([z.date(), z.string().datetime()]),
}).strict();