mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-13 17:40:38 +09:00
[codex] Roll up May 17 branch changes (#6210)
## Thinking Path > - Paperclip is the control plane for autonomous AI companies, so agent work needs visible ownership, recovery, and operator controls. > - This local branch had accumulated several related control-plane reliability and operator-experience fixes across recovery actions, watchdog folding, model-profile defaults, mentions, markdown editing, plugin launchers, and small UI polish. > - The branch needed to be converted into a PR against the current `origin/master` without losing dirty work or including lockfile/workflow churn. > - The safest standalone shape is a single rollup PR because the recovery/server/UI files overlap heavily across the local commits and splitting would create avoidable conflicts. > - This pull request replays the local branch onto latest `origin/master`, preserves the uncommitted work as logical commits, and adds a Zod 4 validator compatibility fix found during verification. > - The benefit is that the May 17 local branch can be reviewed and merged as one coherent, conflict-free branch under the 100-file Greptile limit. ## What Changed - Rebased the local May 17 branch work onto current `origin/master` in a dedicated worktree. - Preserved and committed previously dirty changes for recovery retry handling, plugin/sidebar launcher polish, and `.herenow` ignores. - Added recovery-action behavior for returning source issues to `todo` when retrying source-scoped recovery. - Included the existing local recovery/liveness/watchdog fold, Codex cheap-profile, markdown/mention, duplicate-agent, and UI polish commits from the branch. - Normalized shared validator `z.record(...)` schemas to explicit string-key records for Zod 4 compatibility. - Confirmed the PR has no `pnpm-lock.yaml` or `.github/workflows/*` changes and stays below the 100-file Greptile limit. ## Verification - `pnpm install --frozen-lockfile --ignore-scripts` - `npm run install` in `node_modules/.pnpm/sqlite3@5.1.7/node_modules/sqlite3` to build the local native sqlite3 binding after installing with scripts disabled - `pnpm exec vitest run packages/shared/src/validators/issue.test.ts packages/shared/src/project-mentions.test.ts packages/adapter-utils/src/server-utils.test.ts server/src/__tests__/heartbeat-model-profile.test.ts server/src/__tests__/issue-recovery-actions.test.ts server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts server/src/__tests__/heartbeat-active-run-output-watchdog.test.ts server/src/__tests__/plugin-local-folders.test.ts ui/src/components/IssueRecoveryActionCard.test.tsx ui/src/components/Sidebar.test.tsx ui/src/components/SidebarAccountMenu.test.tsx ui/src/components/IssueProperties.test.tsx ui/src/components/MarkdownEditor.test.tsx ui/src/components/MarkdownBody.test.tsx ui/src/lib/duplicate-agent-payload.test.ts ui/src/pages/Routines.test.tsx` - First pass: 13 files passed with 201 passing tests; 3 server files failed before sqlite3 native binding was built. - After rebuilding sqlite3: `server/src/__tests__/heartbeat-model-profile.test.ts`, `server/src/__tests__/issue-recovery-actions.test.ts`, and `server/src/__tests__/heartbeat-active-run-output-watchdog.test.ts` passed/loaded; embedded Postgres tests were skipped by the local host guard. - `pnpm --filter @paperclipai/shared typecheck` - `pnpm --filter @paperclipai/adapter-utils typecheck` - `pnpm --filter @paperclipai/server typecheck` - `pnpm --filter @paperclipai/ui typecheck` ## Risks - Medium risk: this is a broad rollup PR across recovery semantics, server tests, shared validators, and UI surfaces. - Some embedded Postgres tests skipped locally due the host guard, so CI should provide the stronger database-backed signal. - UI changes were covered by component tests, but no browser screenshot was captured in this PR creation pass. - This branch may overlap with existing recovery/liveness PR work; merge this PR independently or restack/close overlapping branches rather than merging duplicate implementations together. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex, GPT-5-based coding agent, tool-enabled local repository and GitHub workflow, medium reasoning effort. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [ ] If this change affects the UI, I have included before/after screenshots - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
705c1b8d81
commit
d734bd43d1
83 changed files with 3675 additions and 180 deletions
|
|
@ -1,20 +1,57 @@
|
|||
export const REDACTED_COMMAND_TEXT_VALUE = "***REDACTED***";
|
||||
|
||||
const COMMAND_CLI_SECRET_OPTION_RE =
|
||||
/(\B-{1,2}(?:api[-_]?key|(?:access[-_]?|auth[-_]?)?token|token|authorization|bearer|secret|passwd|password|credential|jwt|private[-_]?key|cookie|connectionstring)(?:\s+|=)(["']?))[^\s"'`]+(\2)/gi;
|
||||
const COMMAND_ENV_SECRET_ASSIGNMENT_RE =
|
||||
/(\b[A-Za-z0-9_]*(?:TOKEN|KEY|SECRET|PASSWORD|PASSWD|AUTHORIZATION|JWT)[A-Za-z0-9_]*\s*=\s*)[^\s"'`]+/gi;
|
||||
const SECRET_NAME_PATTERN =
|
||||
String.raw`[A-Za-z0-9_-]*(?:api[-_]?key|(?:access[-_]?|auth[-_]?)?token|token|authorization|bearer|secret|passwd|password|credential|jwt|private[-_]?key|cookie|connectionstring)[A-Za-z0-9_-]*`;
|
||||
|
||||
const COMMAND_CLI_SECRET_OPTION_RE = new RegExp(
|
||||
String.raw`(\B-{1,2}${SECRET_NAME_PATTERN}(?:\s+|=)(["']?))[^\s"'` + "`" + String.raw`]+(\2)`,
|
||||
"gi",
|
||||
);
|
||||
const COMMAND_ENV_SECRET_ASSIGNMENT_RE = new RegExp(
|
||||
String.raw`(\b${SECRET_NAME_PATTERN}\s*=\s*)(?:(["'])([^"'` + "`" + String.raw`\r\n]*)\2|([^\s"'` + "`" + String.raw`]+))`,
|
||||
"gi",
|
||||
);
|
||||
const COMMAND_AUTHORIZATION_BEARER_RE = /(\bAuthorization\s*:\s*Bearer\s+)[^\s"'`]+/gi;
|
||||
const COMMAND_OPENAI_KEY_RE = /\bsk-[A-Za-z0-9_-]{12,}\b/g;
|
||||
const COMMAND_GITHUB_TOKEN_RE = /\bgh[pousr]_[A-Za-z0-9_]{20,}\b/g;
|
||||
const COMMAND_JWT_RE =
|
||||
/\b[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}(?:\.[A-Za-z0-9_-]{8,})?\b/g;
|
||||
const COMMAND_SECRET_HINTS = [
|
||||
"api",
|
||||
"key",
|
||||
"token",
|
||||
"auth",
|
||||
"bearer",
|
||||
"secret",
|
||||
"pass",
|
||||
"credential",
|
||||
"jwt",
|
||||
"private",
|
||||
"cookie",
|
||||
"connectionstring",
|
||||
"sk-",
|
||||
"ghp_",
|
||||
"gho_",
|
||||
"ghu_",
|
||||
"ghs_",
|
||||
"ghr_",
|
||||
] as const;
|
||||
|
||||
function maybeContainsSecretText(command: string) {
|
||||
const lower = command.toLowerCase();
|
||||
return COMMAND_SECRET_HINTS.some((hint) => lower.includes(hint)) || command.includes(".");
|
||||
}
|
||||
|
||||
export function redactCommandText(command: string, redactedValue = REDACTED_COMMAND_TEXT_VALUE): string {
|
||||
if (!maybeContainsSecretText(command)) return command;
|
||||
return command
|
||||
.replace(COMMAND_AUTHORIZATION_BEARER_RE, `$1${redactedValue}`)
|
||||
.replace(COMMAND_CLI_SECRET_OPTION_RE, `$1${redactedValue}$3`)
|
||||
.replace(COMMAND_ENV_SECRET_ASSIGNMENT_RE, `$1${redactedValue}`)
|
||||
.replace(
|
||||
COMMAND_ENV_SECRET_ASSIGNMENT_RE,
|
||||
(_match, prefix: string, quote: string | undefined) =>
|
||||
quote ? `${prefix}${quote}${redactedValue}${quote}` : `${prefix}${redactedValue}`,
|
||||
)
|
||||
.replace(COMMAND_OPENAI_KEY_RE, redactedValue)
|
||||
.replace(COMMAND_GITHUB_TOKEN_RE, redactedValue)
|
||||
.replace(COMMAND_JWT_RE, redactedValue);
|
||||
|
|
|
|||
|
|
@ -53,13 +53,14 @@ describe("buildInvocationEnvForLogs", () => {
|
|||
const loggedEnv = buildInvocationEnvForLogs(
|
||||
{ SAFE_VALUE: "visible" },
|
||||
{
|
||||
resolvedCommand: "env OPENAI_API_KEY=sk-live-example custom-acp --token ghp_example_secret",
|
||||
resolvedCommand:
|
||||
"env OPENAI_API_KEY=sk-live-example PAPERCLIP_API_KEY='paperclip-quoted-secret' custom-acp --paperclip-api-key=paperclip-flag-secret --token ghp_example_secret",
|
||||
},
|
||||
);
|
||||
|
||||
expect(loggedEnv.SAFE_VALUE).toBe("visible");
|
||||
expect(loggedEnv.PAPERCLIP_RESOLVED_COMMAND).toBe(
|
||||
"env OPENAI_API_KEY=***REDACTED*** custom-acp --token ***REDACTED***",
|
||||
"env OPENAI_API_KEY=***REDACTED*** PAPERCLIP_API_KEY='***REDACTED***' custom-acp --paperclip-api-key=***REDACTED*** --token ***REDACTED***",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -52,7 +52,8 @@ export const modelProfiles: AdapterModelProfileDefinition[] = [
|
|||
description: "Use the lowest-cost known Codex local model lane without changing the primary model.",
|
||||
adapterConfig: {
|
||||
model: "gpt-5.3-codex-spark",
|
||||
modelReasoningEffort: "low",
|
||||
// Spark is the cheap lane by model price; high effort keeps Codex coding behavior usable for delegated work.
|
||||
modelReasoningEffort: "high",
|
||||
},
|
||||
source: "adapter_default",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -155,6 +155,11 @@ type ManagedRoutine = {
|
|||
} | null;
|
||||
};
|
||||
|
||||
type ManagedRoutineDefaultDrift = NonNullable<ManagedRoutine["defaultDrift"]>;
|
||||
type ManagedRoutinesListItemWithDrift = ManagedRoutinesListItem & {
|
||||
defaultDrift?: ManagedRoutineDefaultDrift | null;
|
||||
};
|
||||
|
||||
type ManagedSkill = {
|
||||
status: string;
|
||||
skillId?: string | null;
|
||||
|
|
@ -5905,7 +5910,7 @@ function SettingsBody({ context, initialSection = "root" }: { context: { company
|
|||
const effectiveSelectedProjectId = selectedProjectId || data.managedProject.projectId || "";
|
||||
const currentProjectOption = projectOptions.find((project) => project.id === effectiveSelectedProjectId) ?? projectFallbackOption;
|
||||
const currentEventPolicy = eventPolicy ?? data.eventIngestion;
|
||||
const managedRoutineItems: ManagedRoutinesListItem[] = managedRoutines.map((routine) => {
|
||||
const managedRoutineItems: ManagedRoutinesListItemWithDrift[] = managedRoutines.map((routine) => {
|
||||
const fallback = routineFallbackFor(routine);
|
||||
const key = routine.resourceKey ?? routine.routineId ?? fallback.title;
|
||||
const status = managedRoutineStatus(routine);
|
||||
|
|
@ -6132,7 +6137,7 @@ function SettingsBody({ context, initialSection = "root" }: { context: { company
|
|||
|
||||
async function resetManagedRoutineToDefaults(routine: ManagedRoutinesListItem) {
|
||||
if (!context.companyId || !routine.resourceKey) return;
|
||||
const changedFields = routine.defaultDrift?.changedFields ?? [];
|
||||
const changedFields = (routine as ManagedRoutinesListItemWithDrift).defaultDrift?.changedFields ?? [];
|
||||
const fieldList = changedFields.length > 0 ? changedFields.join(", ") : "managed defaults";
|
||||
const confirmed = typeof window === "undefined" || window.confirm(
|
||||
`Update "${routine.title}" to the current LLM Wiki plugin defaults? This replaces ${fieldList}. Cancel to keep the current custom routine text.`,
|
||||
|
|
|
|||
|
|
@ -1102,10 +1102,10 @@ export async function listPaperclipIngestionCandidates(ctx: PluginContext, input
|
|||
return { projects, rootIssues: issues };
|
||||
}
|
||||
|
||||
export async function updateEventIngestionSettings(
|
||||
ctx: PluginContext,
|
||||
export async function updateEventIngestionSettings(
|
||||
ctx: PluginContext,
|
||||
input: { companyId: string; settings: WikiEventIngestionSettingsUpdate },
|
||||
): Promise<WikiEventIngestionSettings> {
|
||||
): Promise<WikiEventIngestionSettings> {
|
||||
await requirePaperclipIngestionPolicy(ctx, {
|
||||
companyId: input.companyId,
|
||||
wikiId: normalizeWikiId(input.settings.wikiId),
|
||||
|
|
|
|||
|
|
@ -1047,22 +1047,27 @@ export { deriveProjectUrlKey, normalizeProjectUrlKey, hasNonAsciiContent } from
|
|||
export {
|
||||
AGENT_MENTION_SCHEME,
|
||||
PROJECT_MENTION_SCHEME,
|
||||
ROUTINE_MENTION_SCHEME,
|
||||
SKILL_MENTION_SCHEME,
|
||||
USER_MENTION_SCHEME,
|
||||
buildAgentMentionHref,
|
||||
buildProjectMentionHref,
|
||||
buildRoutineMentionHref,
|
||||
buildSkillMentionHref,
|
||||
buildUserMentionHref,
|
||||
extractAgentMentionIds,
|
||||
extractProjectMentionIds,
|
||||
extractRoutineMentionIds,
|
||||
extractSkillMentionIds,
|
||||
extractUserMentionIds,
|
||||
parseAgentMentionHref,
|
||||
parseProjectMentionHref,
|
||||
parseRoutineMentionHref,
|
||||
parseSkillMentionHref,
|
||||
parseUserMentionHref,
|
||||
type ParsedAgentMention,
|
||||
type ParsedProjectMention,
|
||||
type ParsedRoutineMention,
|
||||
type ParsedSkillMention,
|
||||
type ParsedUserMention,
|
||||
} from "./project-mentions.js";
|
||||
|
|
|
|||
|
|
@ -2,14 +2,17 @@ import { describe, expect, it } from "vitest";
|
|||
import {
|
||||
buildAgentMentionHref,
|
||||
buildProjectMentionHref,
|
||||
buildRoutineMentionHref,
|
||||
buildSkillMentionHref,
|
||||
buildUserMentionHref,
|
||||
extractAgentMentionIds,
|
||||
extractProjectMentionIds,
|
||||
extractRoutineMentionIds,
|
||||
extractSkillMentionIds,
|
||||
extractUserMentionIds,
|
||||
parseAgentMentionHref,
|
||||
parseProjectMentionHref,
|
||||
parseRoutineMentionHref,
|
||||
parseSkillMentionHref,
|
||||
parseUserMentionHref,
|
||||
} from "./project-mentions.js";
|
||||
|
|
@ -49,4 +52,12 @@ describe("project-mentions", () => {
|
|||
});
|
||||
expect(extractSkillMentionIds(`[/release-changelog](${href})`)).toEqual(["skill-123"]);
|
||||
});
|
||||
|
||||
it("round-trips routine mentions", () => {
|
||||
const href = buildRoutineMentionHref("routine-123");
|
||||
expect(parseRoutineMentionHref(href)).toEqual({
|
||||
routineId: "routine-123",
|
||||
});
|
||||
expect(extractRoutineMentionIds(`[/routine:Weekly review](${href})`)).toEqual(["routine-123"]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ export const PROJECT_MENTION_SCHEME = "project://";
|
|||
export const AGENT_MENTION_SCHEME = "agent://";
|
||||
export const USER_MENTION_SCHEME = "user://";
|
||||
export const SKILL_MENTION_SCHEME = "skill://";
|
||||
export const ROUTINE_MENTION_SCHEME = "routine://";
|
||||
|
||||
const HEX_COLOR_RE = /^[0-9a-f]{6}$/i;
|
||||
const HEX_COLOR_SHORT_RE = /^[0-9a-f]{3}$/i;
|
||||
|
|
@ -11,6 +12,7 @@ const PROJECT_MENTION_LINK_RE = /\[[^\]]*]\((project:\/\/[^)\s]+)\)/gi;
|
|||
const AGENT_MENTION_LINK_RE = /\[[^\]]*]\((agent:\/\/[^)\s]+)\)/gi;
|
||||
const USER_MENTION_LINK_RE = /\[[^\]]*]\((user:\/\/[^)\s]+)\)/gi;
|
||||
const SKILL_MENTION_LINK_RE = /\[[^\]]*]\((skill:\/\/[^)\s]+)\)/gi;
|
||||
const ROUTINE_MENTION_LINK_RE = /\[[^\]]*]\((routine:\/\/[^)\s]+)\)/gi;
|
||||
const AGENT_ICON_NAME_RE = /^[a-z0-9-]+$/i;
|
||||
const SKILL_SLUG_RE = /^[a-z0-9][a-z0-9-]*$/i;
|
||||
|
||||
|
|
@ -33,6 +35,10 @@ export interface ParsedSkillMention {
|
|||
slug: string | null;
|
||||
}
|
||||
|
||||
export interface ParsedRoutineMention {
|
||||
routineId: string;
|
||||
}
|
||||
|
||||
function normalizeHexColor(input: string | null | undefined): string | null {
|
||||
if (!input) return null;
|
||||
const trimmed = input.trim();
|
||||
|
|
@ -169,6 +175,28 @@ export function parseSkillMentionHref(href: string): ParsedSkillMention | null {
|
|||
};
|
||||
}
|
||||
|
||||
export function buildRoutineMentionHref(routineId: string): string {
|
||||
return `${ROUTINE_MENTION_SCHEME}${routineId.trim()}`;
|
||||
}
|
||||
|
||||
export function parseRoutineMentionHref(href: string): ParsedRoutineMention | null {
|
||||
if (!href.startsWith(ROUTINE_MENTION_SCHEME)) return null;
|
||||
|
||||
let url: URL;
|
||||
try {
|
||||
url = new URL(href);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (url.protocol !== "routine:") return null;
|
||||
|
||||
const routineId = `${url.hostname}${url.pathname}`.replace(/^\/+/, "").trim();
|
||||
if (!routineId) return null;
|
||||
|
||||
return { routineId };
|
||||
}
|
||||
|
||||
export function extractProjectMentionIds(markdown: string): string[] {
|
||||
if (!markdown) return [];
|
||||
const ids = new Set<string>();
|
||||
|
|
@ -217,6 +245,18 @@ export function extractSkillMentionIds(markdown: string): string[] {
|
|||
return [...ids];
|
||||
}
|
||||
|
||||
export function extractRoutineMentionIds(markdown: string): string[] {
|
||||
if (!markdown) return [];
|
||||
const ids = new Set<string>();
|
||||
const re = new RegExp(ROUTINE_MENTION_LINK_RE);
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = re.exec(markdown)) !== null) {
|
||||
const parsed = parseRoutineMentionHref(match[1]);
|
||||
if (parsed) ids.add(parsed.routineId);
|
||||
}
|
||||
return [...ids];
|
||||
}
|
||||
|
||||
function normalizeAgentIcon(input: string | null | undefined): string | null {
|
||||
if (!input) return null;
|
||||
const trimmed = input.trim().toLowerCase();
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export const upsertAgentInstructionsFileSchema = z.object({
|
|||
|
||||
export type UpsertAgentInstructionsFile = z.infer<typeof upsertAgentInstructionsFileSchema>;
|
||||
|
||||
const adapterConfigSchema = z.record(z.unknown()).superRefine((value, ctx) => {
|
||||
const adapterConfigSchema = z.record(z.string(), z.unknown()).superRefine((value, ctx) => {
|
||||
const envValue = value.env;
|
||||
if (envValue === undefined) return;
|
||||
const parsed = envConfigSchema.safeParse(envValue);
|
||||
|
|
@ -46,7 +46,7 @@ const adapterConfigSchema = z.record(z.unknown()).superRefine((value, ctx) => {
|
|||
|
||||
export const createAgentInstructionsBundleSchema = z.object({
|
||||
entryFile: z.string().trim().min(1).optional(),
|
||||
files: z.record(z.string()).refine((files) => Object.keys(files).length > 0, {
|
||||
files: z.record(z.string(), z.string()).refine((files) => Object.keys(files).length > 0, {
|
||||
message: "instructionsBundle.files must contain at least one file",
|
||||
}),
|
||||
});
|
||||
|
|
@ -78,7 +78,7 @@ export const createAgentSchema = z.object({
|
|||
defaultEnvironmentId: z.string().uuid().optional().nullable(),
|
||||
budgetMonthlyCents: z.number().int().nonnegative().optional().default(0),
|
||||
permissions: agentPermissionsSchema.optional(),
|
||||
metadata: z.record(z.unknown()).optional().nullable(),
|
||||
metadata: z.record(z.string(), z.unknown()).optional().nullable(),
|
||||
});
|
||||
|
||||
export type CreateAgent = z.infer<typeof createAgentSchema>;
|
||||
|
|
@ -126,7 +126,7 @@ export const wakeAgentSchema = z.object({
|
|||
source: z.enum(["timer", "assignment", "on_demand", "automation"]).optional().default("on_demand"),
|
||||
triggerDetail: z.enum(["manual", "ping", "callback", "system"]).optional(),
|
||||
reason: z.string().optional().nullable(),
|
||||
payload: z.record(z.unknown()).optional().nullable(),
|
||||
payload: z.record(z.string(), z.unknown()).optional().nullable(),
|
||||
idempotencyKey: z.string().optional().nullable(),
|
||||
forceFreshSession: z.preprocess(
|
||||
(value) => (value === null ? undefined : value),
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { multilineTextSchema } from "./text.js";
|
|||
export const createApprovalSchema = z.object({
|
||||
type: z.enum(APPROVAL_TYPES),
|
||||
requestedByAgentId: z.string().uuid().optional().nullable(),
|
||||
payload: z.record(z.unknown()),
|
||||
payload: z.record(z.string(), z.unknown()),
|
||||
issueIds: z.array(z.string().uuid()).optional(),
|
||||
});
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ export const requestApprovalRevisionSchema = z.object({
|
|||
export type RequestApprovalRevision = z.infer<typeof requestApprovalRevisionSchema>;
|
||||
|
||||
export const resubmitApprovalSchema = z.object({
|
||||
payload: z.record(z.unknown()).optional(),
|
||||
payload: z.record(z.string(), z.unknown()).optional(),
|
||||
});
|
||||
|
||||
export type ResubmitApproval = z.infer<typeof resubmitApprovalSchema>;
|
||||
|
|
|
|||
|
|
@ -67,11 +67,11 @@ export const portabilityAgentManifestEntrySchema = z.object({
|
|||
capabilities: z.string().nullable(),
|
||||
reportsToSlug: z.string().min(1).nullable(),
|
||||
adapterType: z.string().min(1),
|
||||
adapterConfig: z.record(z.unknown()),
|
||||
runtimeConfig: z.record(z.unknown()),
|
||||
permissions: z.record(z.unknown()),
|
||||
adapterConfig: z.record(z.string(), z.unknown()),
|
||||
runtimeConfig: z.record(z.string(), z.unknown()),
|
||||
permissions: z.record(z.string(), z.unknown()),
|
||||
budgetMonthlyCents: z.number().int().nonnegative(),
|
||||
metadata: z.record(z.unknown()).nullable(),
|
||||
metadata: z.record(z.string(), z.unknown()).nullable(),
|
||||
});
|
||||
|
||||
export const portabilitySkillManifestEntrySchema = z.object({
|
||||
|
|
@ -85,7 +85,7 @@ export const portabilitySkillManifestEntrySchema = z.object({
|
|||
sourceRef: z.string().nullable(),
|
||||
trustLevel: z.string().nullable(),
|
||||
compatibility: z.string().nullable(),
|
||||
metadata: z.record(z.unknown()).nullable(),
|
||||
metadata: z.record(z.string(), z.unknown()).nullable(),
|
||||
fileInventory: z.array(z.object({
|
||||
path: z.string().min(1),
|
||||
kind: z.string().min(1),
|
||||
|
|
@ -102,7 +102,7 @@ export const portabilityProjectManifestEntrySchema = z.object({
|
|||
targetDate: z.string().nullable(),
|
||||
color: z.string().nullable(),
|
||||
status: z.string().nullable(),
|
||||
executionWorkspacePolicy: z.record(z.unknown()).nullable(),
|
||||
executionWorkspacePolicy: z.record(z.string(), z.unknown()).nullable(),
|
||||
workspaces: z.array(z.object({
|
||||
key: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
|
|
@ -113,10 +113,10 @@ export const portabilityProjectManifestEntrySchema = z.object({
|
|||
visibility: z.string().nullable(),
|
||||
setupCommand: z.string().nullable(),
|
||||
cleanupCommand: z.string().nullable(),
|
||||
metadata: z.record(z.unknown()).nullable(),
|
||||
metadata: z.record(z.string(), z.unknown()).nullable(),
|
||||
isPrimary: z.boolean(),
|
||||
})).default([]),
|
||||
metadata: z.record(z.unknown()).nullable(),
|
||||
metadata: z.record(z.string(), z.unknown()).nullable(),
|
||||
});
|
||||
|
||||
export const portabilityIssueRoutineTriggerManifestEntrySchema = z.object({
|
||||
|
|
@ -157,15 +157,15 @@ export const portabilityIssueManifestEntrySchema = z.object({
|
|||
description: z.string().nullable(),
|
||||
recurring: z.boolean().default(false),
|
||||
routine: portabilityIssueRoutineManifestEntrySchema.nullable(),
|
||||
legacyRecurrence: z.record(z.unknown()).nullable(),
|
||||
legacyRecurrence: z.record(z.string(), z.unknown()).nullable(),
|
||||
status: z.string().nullable(),
|
||||
priority: z.string().nullable(),
|
||||
labelIds: z.array(z.string().min(1)).default([]),
|
||||
billingCode: z.string().nullable(),
|
||||
executionWorkspaceSettings: z.record(z.unknown()).nullable(),
|
||||
assigneeAdapterOverrides: z.record(z.unknown()).nullable(),
|
||||
executionWorkspaceSettings: z.record(z.string(), z.unknown()).nullable(),
|
||||
assigneeAdapterOverrides: z.record(z.string(), z.unknown()).nullable(),
|
||||
comments: z.array(portabilityIssueCommentManifestEntrySchema).default([]),
|
||||
metadata: z.record(z.unknown()).nullable(),
|
||||
metadata: z.record(z.string(), z.unknown()).nullable(),
|
||||
});
|
||||
|
||||
export const portabilityManifestSchema = z.object({
|
||||
|
|
@ -197,7 +197,7 @@ export const portabilitySourceSchema = z.discriminatedUnion("type", [
|
|||
z.object({
|
||||
type: z.literal("inline"),
|
||||
rootPath: z.string().min(1).optional().nullable(),
|
||||
files: z.record(portabilityFileEntrySchema),
|
||||
files: z.record(z.string(), portabilityFileEntrySchema),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal("github"),
|
||||
|
|
@ -251,7 +251,7 @@ export type CompanyPortabilityPreview = z.infer<typeof companyPortabilityPreview
|
|||
|
||||
export const portabilityAdapterOverrideSchema = z.object({
|
||||
adapterType: z.string().min(1),
|
||||
adapterConfig: z.record(z.unknown()).optional(),
|
||||
adapterConfig: z.record(z.string(), z.unknown()).optional(),
|
||||
});
|
||||
|
||||
export const companyPortabilityImportSchema = companyPortabilityPreviewSchema.extend({
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export const companySkillSchema = z.object({
|
|||
trustLevel: companySkillTrustLevelSchema,
|
||||
compatibility: companySkillCompatibilitySchema,
|
||||
fileInventory: z.array(companySkillFileInventoryEntrySchema).default([]),
|
||||
metadata: z.record(z.unknown()).nullable(),
|
||||
metadata: z.record(z.string(), z.unknown()).nullable(),
|
||||
createdAt: z.coerce.date(),
|
||||
updatedAt: z.coerce.date(),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ const environmentFields = {
|
|||
description: z.string().optional().nullable(),
|
||||
driver: environmentDriverSchema,
|
||||
status: environmentStatusSchema.optional().default("active"),
|
||||
config: z.record(z.unknown()).optional().default({}),
|
||||
metadata: z.record(z.unknown()).optional().nullable(),
|
||||
config: z.record(z.string(), z.unknown()).optional().default({}),
|
||||
metadata: z.record(z.string(), z.unknown()).optional().nullable(),
|
||||
};
|
||||
|
||||
export const createEnvironmentSchema = z.object(environmentFields).strict();
|
||||
|
|
@ -28,8 +28,8 @@ export const updateEnvironmentSchema = z.object({
|
|||
description: z.string().optional().nullable(),
|
||||
driver: environmentDriverSchema.optional(),
|
||||
status: environmentStatusSchema.optional(),
|
||||
config: z.record(z.unknown()).optional(),
|
||||
metadata: z.record(z.unknown()).optional().nullable(),
|
||||
config: z.record(z.string(), z.unknown()).optional(),
|
||||
metadata: z.record(z.string(), z.unknown()).optional().nullable(),
|
||||
}).strict();
|
||||
export type UpdateEnvironment = z.infer<typeof updateEnvironmentSchema>;
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ export const probeEnvironmentConfigSchema = z.object({
|
|||
name: z.string().min(1).optional(),
|
||||
description: z.string().optional().nullable(),
|
||||
driver: environmentDriverSchema,
|
||||
config: z.record(z.unknown()).optional().default({}),
|
||||
metadata: z.record(z.unknown()).optional().nullable(),
|
||||
config: z.record(z.string(), z.unknown()).optional().default({}),
|
||||
metadata: z.record(z.string(), z.unknown()).optional().nullable(),
|
||||
}).strict();
|
||||
export type ProbeEnvironmentConfig = z.infer<typeof probeEnvironmentConfigSchema>;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export const executionWorkspaceConfigSchema = z.object({
|
|||
provisionCommand: z.string().optional().nullable(),
|
||||
teardownCommand: z.string().optional().nullable(),
|
||||
cleanupCommand: z.string().optional().nullable(),
|
||||
workspaceRuntime: z.record(z.unknown()).optional().nullable(),
|
||||
workspaceRuntime: z.record(z.string(), z.unknown()).optional().nullable(),
|
||||
desiredState: z.enum(["running", "stopped", "manual"]).optional().nullable(),
|
||||
serviceStates: z.record(z.enum(["running", "stopped", "manual"])).optional().nullable(),
|
||||
}).strict();
|
||||
|
|
@ -94,7 +94,7 @@ export const workspaceRuntimeServiceSchema = z.object({
|
|||
lastUsedAt: z.coerce.date(),
|
||||
startedAt: z.coerce.date(),
|
||||
stoppedAt: z.coerce.date().nullable(),
|
||||
stopPolicy: z.record(z.unknown()).nullable(),
|
||||
stopPolicy: z.record(z.string(), z.unknown()).nullable(),
|
||||
healthStatus: z.enum(["unknown", "healthy", "unhealthy"]),
|
||||
configIndex: z.number().int().nonnegative().nullable().optional(),
|
||||
createdAt: z.coerce.date(),
|
||||
|
|
@ -125,7 +125,7 @@ export const updateExecutionWorkspaceSchema = z.object({
|
|||
cleanupEligibleAt: z.string().datetime().optional().nullable(),
|
||||
cleanupReason: z.string().optional().nullable(),
|
||||
config: executionWorkspaceConfigSchema.optional().nullable(),
|
||||
metadata: z.record(z.unknown()).optional().nullable(),
|
||||
metadata: z.record(z.string(), z.unknown()).optional().nullable(),
|
||||
}).strict();
|
||||
|
||||
export type UpdateExecutionWorkspace = z.infer<typeof updateExecutionWorkspaceSchema>;
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export const createIssueTreeHoldSchema = z
|
|||
mode: issueTreeControlModeSchema,
|
||||
reason: z.string().trim().min(1).max(1000).optional().nullable(),
|
||||
releasePolicy: issueTreeHoldReleasePolicySchema.optional().nullable(),
|
||||
metadata: z.record(z.unknown()).optional().nullable(),
|
||||
metadata: z.record(z.string(), z.unknown()).optional().nullable(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ export const releaseIssueTreeHoldSchema = z
|
|||
.object({
|
||||
reason: z.string().trim().min(1).max(1000).optional().nullable(),
|
||||
releasePolicy: issueTreeHoldReleasePolicySchema.optional().nullable(),
|
||||
metadata: z.record(z.unknown()).optional().nullable(),
|
||||
metadata: z.record(z.string(), z.unknown()).optional().nullable(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
|
|
|
|||
|
|
@ -73,6 +73,25 @@ describe("issue validators", () => {
|
|||
).toBe(false);
|
||||
});
|
||||
|
||||
it("allows restored recovery resolutions to return the source issue to todo", () => {
|
||||
expect(
|
||||
resolveIssueRecoveryActionSchema.parse({
|
||||
outcome: "restored",
|
||||
sourceIssueStatus: "todo",
|
||||
}),
|
||||
).toMatchObject({
|
||||
outcome: "restored",
|
||||
sourceIssueStatus: "todo",
|
||||
});
|
||||
|
||||
expect(
|
||||
resolveIssueRecoveryActionSchema.safeParse({
|
||||
outcome: "false_positive",
|
||||
sourceIssueStatus: "todo",
|
||||
}).success,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("allows cancelled recovery resolutions to atomically restore the source issue status", () => {
|
||||
expect(
|
||||
resolveIssueRecoveryActionSchema.parse({
|
||||
|
|
|
|||
|
|
@ -116,14 +116,14 @@ export const issueExecutionWorkspaceSettingsSchema = z
|
|||
mode: z.enum(ISSUE_EXECUTION_WORKSPACE_PREFERENCES).optional(),
|
||||
environmentId: z.string().uuid().optional().nullable(),
|
||||
workspaceStrategy: executionWorkspaceStrategySchema.optional().nullable(),
|
||||
workspaceRuntime: z.record(z.unknown()).optional().nullable(),
|
||||
workspaceRuntime: z.record(z.string(), z.unknown()).optional().nullable(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const issueAssigneeAdapterOverridesSchema = z
|
||||
.object({
|
||||
modelProfile: z.enum(MODEL_PROFILE_KEYS).optional(),
|
||||
adapterConfig: z.record(z.unknown()).optional(),
|
||||
adapterConfig: z.record(z.string(), z.unknown()).optional(),
|
||||
useProjectWorkspace: z.boolean().optional(),
|
||||
})
|
||||
.strict();
|
||||
|
|
@ -248,10 +248,10 @@ export const issueRecoveryActionReadModelSchema = z.object({
|
|||
returnOwnerAgentId: z.string().uuid().nullable(),
|
||||
cause: z.string().min(1),
|
||||
fingerprint: z.string().min(1),
|
||||
evidence: z.record(z.unknown()),
|
||||
evidence: z.record(z.string(), z.unknown()),
|
||||
nextAction: z.string().min(1),
|
||||
wakePolicy: z.record(z.unknown()).nullable(),
|
||||
monitorPolicy: z.record(z.unknown()).nullable(),
|
||||
wakePolicy: z.record(z.string(), z.unknown()).nullable(),
|
||||
monitorPolicy: z.record(z.string(), z.unknown()).nullable(),
|
||||
attemptCount: z.number().int().nonnegative(),
|
||||
maxAttempts: z.number().int().positive().nullable(),
|
||||
timeoutAt: z.union([z.date(), z.string().datetime()]).nullable(),
|
||||
|
|
@ -275,14 +275,18 @@ const RESOLVE_ISSUE_RECOVERY_ACTION_OUTCOMES = [
|
|||
export const resolveIssueRecoveryActionSchema = z.object({
|
||||
actionId: z.string().uuid().optional(),
|
||||
outcome: z.enum(RESOLVE_ISSUE_RECOVERY_ACTION_OUTCOMES),
|
||||
sourceIssueStatus: z.enum(["done", "in_review", "blocked"]),
|
||||
sourceIssueStatus: z.enum(["todo", "done", "in_review", "blocked"]),
|
||||
resolutionNote: multilineTextSchema.optional().nullable(),
|
||||
}).strict().superRefine((value, ctx) => {
|
||||
if (value.outcome === "restored") {
|
||||
if (value.sourceIssueStatus !== "done" && value.sourceIssueStatus !== "in_review") {
|
||||
if (
|
||||
value.sourceIssueStatus !== "todo" &&
|
||||
value.sourceIssueStatus !== "done" &&
|
||||
value.sourceIssueStatus !== "in_review"
|
||||
) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Restored recovery actions must move the source issue to done or in_review",
|
||||
message: "Restored recovery actions must move the source issue to todo, done, or in_review",
|
||||
path: ["sourceIssueStatus"],
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ import { routineVariableSchema } from "./routine.js";
|
|||
*
|
||||
* @see PLUGIN_SPEC.md §10.1 — Manifest shape
|
||||
*/
|
||||
export const jsonSchemaSchema = z.record(z.unknown()).refine(
|
||||
export const jsonSchemaSchema = z.record(z.string(), z.unknown()).refine(
|
||||
(val) => {
|
||||
// Must have a "type" field if non-empty, or be a valid JSON Schema object
|
||||
if (Object.keys(val).length === 0) return true;
|
||||
|
|
@ -143,9 +143,9 @@ export const pluginManagedAgentDeclarationSchema = z.object({
|
|||
capabilities: z.string().max(2000).nullable().optional(),
|
||||
adapterType: z.string().min(1).max(100).optional(),
|
||||
adapterPreference: z.array(z.string().min(1).max(100)).max(10).optional(),
|
||||
adapterConfig: z.record(z.unknown()).optional(),
|
||||
runtimeConfig: z.record(z.unknown()).optional(),
|
||||
permissions: z.record(z.unknown()).optional(),
|
||||
adapterConfig: z.record(z.string(), z.unknown()).optional(),
|
||||
runtimeConfig: z.record(z.string(), z.unknown()).optional(),
|
||||
permissions: z.record(z.string(), z.unknown()).optional(),
|
||||
status: z.enum(["idle", "paused"]).optional(),
|
||||
budgetMonthlyCents: z.number().int().min(0).optional(),
|
||||
instructions: z.object({
|
||||
|
|
@ -166,7 +166,7 @@ export const pluginManagedProjectDeclarationSchema = z.object({
|
|||
description: z.string().max(2000).nullable().optional(),
|
||||
status: z.enum(["backlog", "planned", "in_progress", "completed", "cancelled"]).optional(),
|
||||
color: z.string().max(32).nullable().optional(),
|
||||
settings: z.record(z.unknown()).optional(),
|
||||
settings: z.record(z.string(), z.unknown()).optional(),
|
||||
});
|
||||
|
||||
export type PluginManagedProjectDeclarationInput = z.infer<typeof pluginManagedProjectDeclarationSchema>;
|
||||
|
|
@ -373,7 +373,7 @@ const launcherBoundsByEnvironment: Record<
|
|||
export const pluginLauncherActionDeclarationSchema = z.object({
|
||||
type: z.enum(PLUGIN_LAUNCHER_ACTIONS),
|
||||
target: z.string().min(1),
|
||||
params: z.record(z.unknown()).optional(),
|
||||
params: z.record(z.string(), z.unknown()).optional(),
|
||||
}).superRefine((value, ctx) => {
|
||||
if (value.type === "performAction" && value.target.includes("/")) {
|
||||
ctx.addIssue({
|
||||
|
|
@ -993,7 +993,7 @@ export type InstallPlugin = z.infer<typeof installPluginSchema>;
|
|||
* the plugin's instanceConfigSchema is done at the service layer.
|
||||
*/
|
||||
export const upsertPluginConfigSchema = z.object({
|
||||
configJson: z.record(z.unknown()),
|
||||
configJson: z.record(z.string(), z.unknown()),
|
||||
});
|
||||
|
||||
export type UpsertPluginConfig = z.infer<typeof upsertPluginConfigSchema>;
|
||||
|
|
@ -1003,7 +1003,7 @@ export type UpsertPluginConfig = z.infer<typeof upsertPluginConfigSchema>;
|
|||
* Allows a partial merge of config values.
|
||||
*/
|
||||
export const patchPluginConfigSchema = z.object({
|
||||
configJson: z.record(z.unknown()),
|
||||
configJson: z.record(z.string(), z.unknown()),
|
||||
});
|
||||
|
||||
export type PatchPluginConfig = z.infer<typeof patchPluginConfigSchema>;
|
||||
|
|
|
|||
|
|
@ -21,16 +21,16 @@ export const projectExecutionWorkspacePolicySchema = z
|
|||
defaultProjectWorkspaceId: z.string().uuid().optional().nullable(),
|
||||
environmentId: z.string().uuid().optional().nullable(),
|
||||
workspaceStrategy: executionWorkspaceStrategySchema.optional().nullable(),
|
||||
workspaceRuntime: z.record(z.unknown()).optional().nullable(),
|
||||
branchPolicy: z.record(z.unknown()).optional().nullable(),
|
||||
pullRequestPolicy: z.record(z.unknown()).optional().nullable(),
|
||||
runtimePolicy: z.record(z.unknown()).optional().nullable(),
|
||||
cleanupPolicy: z.record(z.unknown()).optional().nullable(),
|
||||
workspaceRuntime: z.record(z.string(), z.unknown()).optional().nullable(),
|
||||
branchPolicy: z.record(z.string(), z.unknown()).optional().nullable(),
|
||||
pullRequestPolicy: z.record(z.string(), z.unknown()).optional().nullable(),
|
||||
runtimePolicy: z.record(z.string(), z.unknown()).optional().nullable(),
|
||||
cleanupPolicy: z.record(z.string(), z.unknown()).optional().nullable(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const projectWorkspaceRuntimeConfigSchema = z.object({
|
||||
workspaceRuntime: z.record(z.unknown()).optional().nullable(),
|
||||
workspaceRuntime: z.record(z.string(), z.unknown()).optional().nullable(),
|
||||
desiredState: z.enum(["running", "stopped", "manual"]).optional().nullable(),
|
||||
serviceStates: z.record(z.enum(["running", "stopped", "manual"])).optional().nullable(),
|
||||
}).strict();
|
||||
|
|
@ -51,7 +51,7 @@ const projectWorkspaceFields = {
|
|||
remoteProvider: z.string().optional().nullable(),
|
||||
remoteWorkspaceRef: z.string().optional().nullable(),
|
||||
sharedWorkspaceKey: z.string().optional().nullable(),
|
||||
metadata: z.record(z.unknown()).optional().nullable(),
|
||||
metadata: z.record(z.string(), z.unknown()).optional().nullable(),
|
||||
runtimeConfig: projectWorkspaceRuntimeConfigSchema.optional().nullable(),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -146,8 +146,8 @@ export type UpdateRoutineTrigger = z.infer<typeof updateRoutineTriggerSchema>;
|
|||
|
||||
export const runRoutineSchema = z.object({
|
||||
triggerId: z.string().uuid().optional().nullable(),
|
||||
payload: z.record(z.unknown()).optional().nullable(),
|
||||
variables: z.record(routineVariableValueSchema).optional().nullable(),
|
||||
payload: z.record(z.string(), z.unknown()).optional().nullable(),
|
||||
variables: z.record(z.string(), routineVariableValueSchema).optional().nullable(),
|
||||
projectId: z.string().uuid().optional().nullable(),
|
||||
assigneeAgentId: z.string().uuid().optional().nullable(),
|
||||
idempotencyKey: z.string().trim().max(255).optional().nullable(),
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export const envBindingSchema = z.union([
|
|||
envBindingSecretRefSchema,
|
||||
]);
|
||||
|
||||
export const envConfigSchema = z.record(envBindingSchema);
|
||||
export const envConfigSchema = z.record(z.string(), envBindingSchema);
|
||||
|
||||
export const createSecretSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
|
|
@ -36,7 +36,7 @@ export const createSecretSchema = z.object({
|
|||
value: z.string().min(1).optional().nullable(),
|
||||
description: z.string().optional().nullable(),
|
||||
externalRef: z.string().optional().nullable(),
|
||||
providerMetadata: z.record(z.unknown()).optional().nullable(),
|
||||
providerMetadata: z.record(z.string(), z.unknown()).optional().nullable(),
|
||||
providerVersionRef: z.string().optional().nullable(),
|
||||
}).superRefine((value, ctx) => {
|
||||
if ((value.managedMode ?? "paperclip_managed") === "external_reference") {
|
||||
|
|
@ -83,7 +83,7 @@ export const updateSecretSchema = z.object({
|
|||
providerConfigId: z.string().uuid().optional().nullable(),
|
||||
description: z.string().optional().nullable(),
|
||||
externalRef: z.string().optional().nullable(),
|
||||
providerMetadata: z.record(z.unknown()).optional().nullable(),
|
||||
providerMetadata: z.record(z.string(), z.unknown()).optional().nullable(),
|
||||
});
|
||||
|
||||
export type UpdateSecret = z.infer<typeof updateSecretSchema>;
|
||||
|
|
@ -198,7 +198,7 @@ export const createSecretProviderConfigSchema = z.object({
|
|||
displayName: z.string().trim().min(1).max(120),
|
||||
status: z.enum(SECRET_PROVIDER_CONFIG_STATUSES).optional(),
|
||||
isDefault: z.boolean().optional(),
|
||||
config: z.record(z.unknown()).default({}),
|
||||
config: z.record(z.string(), z.unknown()).default({}),
|
||||
}).superRefine((value, ctx) => {
|
||||
rejectSensitiveProviderConfigKeys(value.config, ctx);
|
||||
const parsed = secretProviderConfigPayloadSchema.safeParse({
|
||||
|
|
@ -236,7 +236,7 @@ export const updateSecretProviderConfigSchema = z.object({
|
|||
displayName: z.string().trim().min(1).max(120).optional(),
|
||||
status: z.enum(SECRET_PROVIDER_CONFIG_STATUSES).optional(),
|
||||
isDefault: z.boolean().optional(),
|
||||
config: z.record(z.unknown()).optional(),
|
||||
config: z.record(z.string(), z.unknown()).optional(),
|
||||
}).superRefine((value, ctx) => {
|
||||
if (value.config !== undefined) {
|
||||
rejectSensitiveProviderConfigKeys(value.config, ctx);
|
||||
|
|
@ -268,7 +268,7 @@ export const remoteSecretImportSelectionSchema = z.object({
|
|||
key: z.string().trim().min(1).max(120).regex(/^[a-zA-Z0-9_.-]+$/).optional().nullable(),
|
||||
description: z.string().trim().max(500).optional().nullable(),
|
||||
providerVersionRef: z.string().trim().min(1).max(512).optional().nullable(),
|
||||
providerMetadata: z.record(z.unknown()).optional().nullable(),
|
||||
providerMetadata: z.record(z.string(), z.unknown()).optional().nullable(),
|
||||
});
|
||||
|
||||
export const remoteSecretImportSchema = z.object({
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export const createIssueWorkProductSchema = z.object({
|
|||
isPrimary: z.boolean().optional().default(false),
|
||||
healthStatus: z.enum(["unknown", "healthy", "unhealthy"]).optional().default("unknown"),
|
||||
summary: z.string().optional().nullable(),
|
||||
metadata: z.record(z.unknown()).optional().nullable(),
|
||||
metadata: z.record(z.string(), z.unknown()).optional().nullable(),
|
||||
createdByRunId: z.string().uuid().optional().nullable(),
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue