Reconcile Claude platform packages with merged Codex defaults

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* codex/claude-runner-default: (85 commits)
  feat: default new Codex agents to Paperclip Runner on Linux and macOS (#15683)
  docs(skills): add slow-query diagnosis and fix skill (#16303)
  feat(sentry): add opt-in run failure reporting with saved run references (#16300)
  refactor(server): isolate provider message admission and runtime policies (#16289)
  refactor(server): isolate provider webhook lifecycle decisions (#16283)
  fix(workspaces): keep cleanup Git scans fresh and dedupe page reads (#16302)
  fix(sentry): keep provider-rejected Grok wakes out of run failure reports (#16297)
  fix(runner): report Runner disconnect age when a Dot bridge exits (#16296)
  refactor(server): isolate chat conversation and control policies (#16276)
  perf(recovery): use indexed execution identity lookups (#16291)
  perf(runs): bound history and reduce repeated context reads (#16286)
  fix(native-runtime): report which close barrier failed (#16298)
  feat(skills): select and validate tracked GitHub branches (#16288)
  fix(observability): sample native runs independently of triggers (#16285)
  fix(github): retry and recover review publication (#16284)
  perf(workspaces): wake terminal cleanup from committed changes (#16282)
  fix(mcp): deduplicate discovered tool descriptors (#16281)
  perf: remove history scans from task and chat queries (#16278)
  docs(skills): add maintenance bot setup workflow (#16279)
  fix(observability): time execution configuration steps (#16236)
  ...

# Conflicts:
#	scripts/verify-grok-npm-install.mjs
This commit is contained in:
Dotta committed 2026-10-14 12:12:58 -05:00
commit b030683ca2
1176 files changed
+129461 -261897

No files matched your search

-6
View File
@@ -1,4 +1,3 @@
/** Request intent only. Agents persist the selected adapter, not an automatic choice. */
export type AgentRunnerChoice = "auto" | "paperclip" | "legacy";
export type AgentRunner = Exclude<AgentRunnerChoice, "auto">;
@@ -13,12 +12,10 @@ export function paperclipRunnerProfileForHarness(harness: string): { provider: "
return undefined;
}
/** Highest required GLIBC version in the shipped GNU/Linux daemon's ELF version needs. */
export const PAPERCLIP_RUNNER_MINIMUM_GLIBC_VERSION = "2.39";
function supportsLinuxLibc(libc?: "glibc" | "musl" | "unknown", version?: string): boolean {
if (libc !== undefined && libc !== "glibc") return false;
// Source-only/platform callers do not assert a measured host libc version.
if (version === undefined) return true;
const match = typeof version === "string" ? version.trim().match(/^(?:glibc\s+)?(\d+)\.(\d+)(?:\.\d+)?$/) : null;
if (!match) return false;
@@ -28,15 +25,12 @@ function supportsLinuxLibc(libc?: "glibc" | "musl" | "unknown", version?: string
return major > requiredMajor! || (major === requiredMajor && minor >= requiredMinor!);
}
/** Creation defaults follow GNU/Linux and macOS artifacts; Alpine/musl is deferred. */
export function paperclipRunnerSupportsPlatform(harness: string, platform: string, architecture: string, libc?: "glibc" | "musl" | "unknown", glibcVersion?: string): boolean {
return !!paperclipRunnerProfileForHarness(harness)
// Omitted libc preserves platform-only callers; detected unknown Linux is not qualified.
&& ((platform === "linux" && supportsLinuxLibc(libc, glibcVersion) && (architecture === "x64" || architecture === "arm64"))
|| (platform === "darwin" && (architecture === "arm64" || architecture === "x64")));
}
/** Recover harness identity without changing execution or guessing unknown providers. */
export function agentHarnessType(adapterType: string, config: Record<string, unknown> = {}): string {
if (adapterType !== "paperclip_runner") return adapterType;
if (config.provider === undefined || config.provider === "codex") return "codex_local";
@@ -100,5 +100,6 @@ import a98 from "./app-definitions/messages-api.json" with { type: "json" };
import a99 from "./app-definitions/chat-completions-api.json" with { type: "json" };
import a100 from "./app-definitions/local.json" with { type: "json" };
import a101 from "./app-definitions/monday.json" with { type: "json" };
import a102 from "./app-definitions/speko.json" with { type: "json" };
import type { AppDefinition } from "./types/app-definition.js";
export const APP_DEFINITIONS=[a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31,a32,a33,a34,a35,a36,a37,a38,a39,a40,a41,a42,a43,a44,a45,a46,a47,a48,a49,a50,a51,a52,a53,a54,a55,a56,a57,a58,a59,a60,a61,a62,a63,a64,a65,a66,a67,a68,a69,a70,a71,a72,a73,a74,a75,a76,a77,a78,a79,a80,a81,a82,a83,a84,a85,a86,a87,a88,a89,a90,a91,a92,a93,a94,a95,a96,a97,a98,a99,a100,a101] as AppDefinition[];
export const APP_DEFINITIONS=[a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31,a32,a33,a34,a35,a36,a37,a38,a39,a40,a41,a42,a43,a44,a45,a46,a47,a48,a49,a50,a51,a52,a53,a54,a55,a56,a57,a58,a59,a60,a61,a62,a63,a64,a65,a66,a67,a68,a69,a70,a71,a72,a73,a74,a75,a76,a77,a78,a79,a80,a81,a82,a83,a84,a85,a86,a87,a88,a89,a90,a91,a92,a93,a94,a95,a96,a97,a98,a99,a100,a101,a102] as AppDefinition[];
+1 -1
View File
@@ -873,7 +873,7 @@ describe("AppDefinition catalog", () => {
"ticktick",
"xero",
]);
expect(APP_STORE_DEFINITIONS).toHaveLength(79);
expect(APP_STORE_DEFINITIONS).toHaveLength(80);
const connectableSlugs = new Set(
CONNECTABLE_APP_DEFINITIONS.map((entry) => entry.slug),
);
+1
View File
@@ -35,6 +35,7 @@ export const CONNECTABLE_APP_SLUGS = new Set([
"github",
"github-code-review-bot",
"discord",
"speko",
"microsoft-teams",
"telegram",
"imessage-photon",
@@ -0,0 +1,56 @@
{
"schemaVersion": 1,
"slug": "speko",
"name": "Speko",
"description": "Talk with a Paperclip agent through an experimental hosted voice connection.",
"categories": [
"communication"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/speko.svg"
},
"urlPatterns": [
"https://speko.ai/*",
"https://platform.speko.ai/*"
],
"methods": [
{
"key": "voice-agent",
"label": "Talk with an agent",
"purpose": "channel",
"provider": "speko",
"transport": "voice",
"auth": "api_key",
"ownershipModes": [
"customer"
],
"whenToUse": "Use browser voice to start and continue work with a selected Paperclip agent.",
"credentialFields": [
{
"key": "apiKey",
"label": "Speko API key",
"type": "password",
"required": true,
"placeholder": "Paste your Speko API key",
"secret": true
},
{
"key": "agentId",
"label": "Speko agent ID",
"type": "text",
"required": true,
"placeholder": "agent_…",
"secret": false
}
],
"guidanceMd": "Create a dedicated Speko voice persona and choose its voice. Paperclip registers signed session tools and keeps the selected Paperclip agent responsible for task work. Public HTTPS callbacks are required. Review provider recording settings; Speko charges are separate from agent costs. Phone calling is withheld pending live qualification.",
"consoleLinks": {
"register": "https://platform.speko.ai/agents",
"docs": "https://docs.speko.ai/guides/tool-calling"
},
"riskTier": "S3",
"requiredResourceFilters": []
}
]
}
+3 -1
View File
@@ -732,7 +732,7 @@ export type SecretProviderConfigHealthStatus =
export const SECRET_STATUSES = ["active", "disabled", "archived", "deleted"] as const;
export type SecretStatus = (typeof SECRET_STATUSES)[number];
export const SECRET_SCOPES = ["company", "user"] as const;
export const SECRET_SCOPES = ["company", "user", "instance"] as const;
export type SecretScope = (typeof SECRET_SCOPES)[number];
export const SECRET_MANAGED_MODES = ["paperclip_managed", "external_reference"] as const;
@@ -1496,6 +1496,8 @@ export const PLUGIN_UI_SLOT_TYPES = [
export type PluginUiSlotType = (typeof PLUGIN_UI_SLOT_TYPES)[number];
export const WORKSPACE_OVERVIEW_DEFAULT_LIMIT = 50;
export const HEARTBEAT_RUN_LIST_DEFAULT_LIMIT = 200;
export const HEARTBEAT_RUN_LIST_MAX_LIMIT = 1000;
export const WORKSPACE_OVERVIEW_MAX_LIMIT = 100;
export const WORKSPACE_OVERVIEW_LINKED_ISSUE_LIMIT = 4;
@@ -3,3 +3,5 @@ export const EXTERNAL_AGENT_TOOL_GUIDANCE = `When you receive a Paperclip assign
/** The assignment catalog constrains the Paperclip bridge, not the external runtime. */
export const DOT_AGENT_TOOL_GUIDANCE = `${EXTERNAL_AGENT_TOOL_GUIDANCE}\n\nUse the assignment catalog through paperclip_dot_tool for Paperclip operations. Use your native tools directly for the rest of the work; they do not need to appear in that catalog. Record useful results and deliverables on the task and follow the Paperclip completion instructions.`;
export const GROK_BOT_AGENT_TOOL_GUIDANCE = `${EXTERNAL_AGENT_TOOL_GUIDANCE}\n\nUse the current assignment catalog through paperclip_grok_tool for first-party Paperclip operations. Grok Bot retains its own connected apps and persistent memory. Retrieve current task context and instructions for each Paperclip work session; do not assume provider memory is isolated or reset. Acknowledge routine wakes using paperclip_grok_ack_wake. Accept admitted assignments before task operations and keep request IDs stable across retries. Report progress, questions and deliverables in Paperclip, then follow its canonical completion instructions. Routine acceptance, Paperclip session completion and remote process termination are distinct. Grok usage and spending are not reported.`;
+7
View File
@@ -134,6 +134,13 @@ export const INSTANCE_FEATURE_CATALOG: Record<InstanceFeatureKey, FeatureCatalog
cloudDefault: false,
selfHostedDefault: false,
},
enableGrokBot: {
title: "Grok Bot",
description: "Connect an existing Grok Bot as an experimental Paperclip agent. Requires Assistant connections (MCP), Paperclip Runner, and an authenticated public HTTPS instance. Provider spending is not reported.",
tier: "managed",
cloudDefault: false,
selfHostedDefault: false,
},
enableChatConnectors: {
title: "Chat connectors",
description:
+38
View File
@@ -0,0 +1,38 @@
import { z } from "zod";
import { AGENT_ROLES } from "./constants.js";
import type { DotBinding, DotConnection, DotInvitation } from "./dot-invitations.js";
export const createGrokInvitationSchema = z.object({
name: z.string().trim().min(1).max(100),
botName: z.string().trim().min(1).max(100),
role: z.enum(AGENT_ROLES).default("general"),
reportsTo: z.uuid().nullable().optional(),
}).strict();
export const configureGrokRoutineSchema = z.object({
bindingId: z.uuid(), expectedRevision: z.number().int().nonnegative(),
botName: z.string().trim().min(1).max(100),
webhookUrl: z.string().min(1).max(2048), webhookKey: z.string().min(1).max(16384),
}).strict();
export const verifyGrokRoutineSchema = z.object({ bindingId: z.uuid(), requestId: z.uuid() }).strict();
export const retryGrokDeliverySchema = z.object({ correlationId: z.uuid() }).strict();
export const pairGrokBindingSchema = z.object({ replaceBindingId: z.uuid().optional() }).strict();
export const stopGrokWorkSchema = z.object({ expectedRunId: z.uuid().optional() }).strict();
export const recoverGrokRoutineSchema = z.object({ inspectedRemoteWork: z.literal(true) }).strict();
export type CreateGrokInvitation = z.input<typeof createGrokInvitationSchema>;
export type ConfigureGrokRoutine = z.infer<typeof configureGrokRoutineSchema>;
export interface GrokRoutineState {
bindingId: string; botName: string; revision: number; configured: boolean;
verified: boolean; verifiedAt: string | null; lastContactAt: string | null; recoveryNeeded: boolean;
lastExchange: { id: string; kind: "verify" | "mailbox_updated" | "authority_revoked";
status: "prepared" | "sending" | "accepted" | "rejected" | "unknown" | "acknowledged" | "cancelled";
httpStatus: number | null; sentAt: string | null; acknowledgedAt: string | null; expiresAt: string } | null;
}
export interface GrokBinding extends DotBinding {
providerKind: "grok_bot"; wakeVerified: boolean; routine: GrokRoutineState | null;
assignment?: { id: string; runId: string; status: string; lastActivityAt: string; attentionRequired: boolean } | null;
}
export interface GrokInvitation extends Omit<DotInvitation, "agent" | "binding"> {
agent: DotInvitation["agent"] & { role: typeof AGENT_ROLES[number]; reportsTo: string | null; botName: string };
binding: GrokBinding | null;
}
export interface GrokConnection extends Omit<DotConnection, "binding"> { binding: GrokBinding | null; recoveryNeeded: boolean; queuedTaskCount?: number; currentTask?: { id: string; identifier: string; title: string } | null }
+8 -1
View File
@@ -584,6 +584,8 @@ export {
PLUGIN_API_ROUTE_AUTH_MODES,
PLUGIN_API_ROUTE_CHECKOUT_POLICIES,
WORKSPACE_OVERVIEW_DEFAULT_LIMIT,
HEARTBEAT_RUN_LIST_DEFAULT_LIMIT,
HEARTBEAT_RUN_LIST_MAX_LIMIT,
WORKSPACE_OVERVIEW_MAX_LIMIT,
WORKSPACE_OVERVIEW_LINKED_ISSUE_LIMIT,
PIPELINE_AUTOMATION_DEFAULT_TITLE_TEMPLATE,
@@ -1999,6 +2001,7 @@ export {
resolveIssueRecoveryActionSchema,
isValidExistingBranchName,
retryWorkspaceExportSchema,
resumeWorkspaceFinalizationSchema,
issueReviewRequestSchema,
issueExecutionWorkspaceSettingsSchema,
checkoutIssueSchema,
@@ -2868,7 +2871,7 @@ export * from "./agent-avatar-upload.js";
export type { DotBinding, DotInvitation, DotConnection, DotPairing } from "./dot-invitations.js";
export { agentHarnessType, agentRunner, paperclipRunnerProfileForHarness, paperclipRunnerSupportsPlatform, PAPERCLIP_RUNNER_MINIMUM_GLIBC_VERSION, type AgentRunnerChoice, type AgentRunner, type AgentRunnerAvailability } from "./agent-runner.js";
export { EXTERNAL_AGENT_TOOL_GUIDANCE, DOT_AGENT_TOOL_GUIDANCE } from "./external-agent-guidance.js";
export { EXTERNAL_AGENT_TOOL_GUIDANCE, DOT_AGENT_TOOL_GUIDANCE, GROK_BOT_AGENT_TOOL_GUIDANCE } from "./external-agent-guidance.js";
export { AGENT_LIFECYCLE_STATES, type AgentLifecycleState, type AgentLifecycleOperation } from "./types/agent-lifecycle.js";
@@ -2880,3 +2883,7 @@ export * from "./validators/task-workspace.js";
export { prepareWorkspaceRepositorySchema, type PrepareWorkspaceRepository } from "./validators/workspace-repository.js";
export * from "./fast-response.js";
export { HUMAN_TASK_ORIGIN_KINDS, isAgentCreatedIssue, type IssueCreationSourceFields } from "./issue-creation-source.js";
export * from "./grok-bot.js";
@@ -0,0 +1,20 @@
import { describe, expect, it } from "vitest";
import { isAgentCreatedIssue } from "./issue-creation-source.js";
describe("task creation source", () => {
it.each(["manual", "onboarding_first_task"])("preserves human-created %s tasks", (originKind) => {
expect(isAgentCreatedIssue({ createdByUserId: "person", originKind })).toBe(false);
});
it.each(["chat_channel", "routine_execution", "stranded_issue_recovery", "task_watchdog", "harness_liveness_escalation", "plugin:workflow:operation", "built_in_agent_bundle", "pipeline_automation"])("classifies %s as automated even with human attribution", (originKind) => {
expect(isAgentCreatedIssue({ createdByUserId: "person", originKind })).toBe(true);
});
it.each([null, "person"])("classifies channel work as automated with creator %s", (createdByUserId) => {
expect(isAgentCreatedIssue({ originKind: "chat_channel", createdByUserId })).toBe(true);
});
it("recognizes agents and creator-less ordinary tasks", () => {
expect(isAgentCreatedIssue({ createdByAgentId: "agent", originKind: "manual" })).toBe(true);
expect(isAgentCreatedIssue({ originKind: "manual" })).toBe(true);
expect(isAgentCreatedIssue({ createdByUserId: "person", createdByAgentId: "agent" })).toBe(true);
expect(isAgentCreatedIssue({ createdByUserId: "person" })).toBe(false);
});
});
@@ -0,0 +1,17 @@
/** Origins that represent a person's ordinary task or onboarding request. */
export const HUMAN_TASK_ORIGIN_KINDS = ["manual", "onboarding_first_task"] as const;
export interface IssueCreationSourceFields {
createdByAgentId?: string | null;
createdByUserId?: string | null;
originKind?: string | null;
}
/** Creation is independent of who is assigned or responsible for execution. */
export function isAgentCreatedIssue(issue: IssueCreationSourceFields): boolean {
return Boolean(issue.createdByAgentId)
|| !issue.createdByUserId
|| !HUMAN_TASK_ORIGIN_KINDS.includes(
(issue.originKind ?? "manual") as typeof HUMAN_TASK_ORIGIN_KINDS[number],
);
}
+4
View File
@@ -35,6 +35,8 @@ export interface McpConnectionSetup {
}
export interface McpConnection {
companyPrefix?: string;
agent?: { id: string; name: string; provider: string } | null;
id: string;
companyId: string;
clientName: string;
@@ -48,6 +50,8 @@ export interface McpConnection {
/** Code-scoped details shown before activating an operator-approved Dot agent. */
export interface McpDotPairingPreview {
providerKind?: "openai_dot" | "grok_bot";
authorizingPerson?: string;
company: { id: string; name: string };
agent: { id: string; name: string };
permissions: string;
@@ -23,9 +23,11 @@ export interface AgentLifecycleRequest {
operationId: string;
version: number;
phase: AgentLifecycleState;
instanceSecrets?: Record<string, string>;
}
export interface AgentLifecycleResult {
operationId: string;
version: number;
status: "complete" | "pending";
instanceSecrets?: Record<string, string | null>;
}
+1
View File
@@ -109,6 +109,7 @@ export interface Agent {
lastHeartbeatAt: Date | null;
metadata: Record<string, unknown> | null;
orgChainHealth?: AgentOrgChainHealth;
createdByUserId?: string | null;
createdAt: Date;
updatedAt: Date;
}
@@ -55,6 +55,7 @@ export interface CompanySkill {
authorName: string | null;
homepageUrl: string | null;
categories: string[];
accessSourceId?: string | null;
sharingScope: CompanySkillSharingScope;
publicShareToken: string | null;
forkedFromSkillId: string | null;
@@ -89,6 +90,7 @@ export interface CompanySkillListItem {
authorName: string | null;
homepageUrl: string | null;
categories: string[];
accessSourceId?: string | null;
sharingScope: CompanySkillSharingScope;
publicShareToken: string | null;
forkedFromSkillId: string | null;
@@ -13,6 +13,7 @@ export interface ExecutionBlocker {
canRetry?: boolean;
/** Required workspace files must be recovered before another provider turn. */
workspaceRepairRequired?: boolean;
canRecordWorkspaceRepair?: boolean;
savedMessageCount?: number;
}
+2
View File
@@ -154,6 +154,8 @@ export interface GitWorktreeBranchIncoherenceEvidence {
}
export interface HeartbeatRun {
/** Persisted native driver identity; older servers may omit this field. */
driverKind?: string | null;
execution?: import("./execution-projection.js").ExecutionProjection | null;
id: string;
companyId: string;
+2
View File
@@ -77,6 +77,8 @@ export interface InstanceExperimentalSettings {
enablePublicMcp: boolean;
/** Enable the standalone Dot choice and dedicated agent MCP connection independently of the general Runner rollout. */
enableOpenAiDot: boolean;
/** Enable experimental Grok Bot agents; requires MCP and the managed/native Runner. */
enableGrokBot: boolean;
/** @deprecated Compatibility key only. MCP aggregators are always enabled. */
enableMcpAggregators: boolean;
/** Show experimental memory connection setup. Existing connections remain usable. */
+2
View File
@@ -5,5 +5,7 @@ export interface LiveEvent {
companyId: string;
type: LiveEventType;
createdAt: string;
/** Viewer-specific WebSocket projection; absent/false means no task toast. */
toastEligible?: boolean;
payload: Record<string, unknown>;
}
+2
View File
@@ -689,6 +689,8 @@ export interface PaperclipPluginManifestV1 {
capabilities: PluginCapability[];
/** Required participant in agent preparation, stop, resume, and cleanup. */
agentLifecycle?: true;
/** Durable, acknowledged resource events delivered to onResourceLifecycle. Requires events.subscribe. */
resourceLifecycle?: Array<"agent" | "project">;
/** Opt into the native pooled-connection catalog, setup, and management UI. */
aiConnectionRouter?: { name: string; description: string };
/** Entrypoint paths relative to the package root. */
+45
View File
@@ -67,13 +67,48 @@ export interface SkillSource {
connectionId: string | null;
excludedFolders: string[];
enabled: boolean;
automaticUpdates?: boolean;
importNewSkills?: boolean;
accessScope?: 'company' | 'projects';
trackingActive?: boolean;
syncState?: 'pending' | 'refreshing' | 'ready' | 'error' | 'paused';
projects?: Array<{ id: string; name: string; visibility: 'open' | 'private'; active: boolean }>;
revision: number;
lastAttemptAt: Date | null;
lastSuccessAt: Date | null;
lastScanCommit: string | null;
lastError: string | null;
repairAction?: 'connection' | 'branch' | 'retry' | null;
entries: SkillSourceEntry[];
}
export interface SkillSourceSettingsRequest {
revision: number;
automaticUpdates?: boolean;
importNewSkills?: boolean;
connectionId?: string | null;
/** Follow this branch/ref, or HEAD for the default branch. Version pins remain intact. */
trackingRef?: string;
}
export interface SkillSourceBranchesQuery { connectionId?: string }
export interface SkillSourceBranches {
defaultBranch: string;
branches: Array<{ name: string; isDefault: boolean }>;
/** The bounded list may omit branches; an unlisted name can still be entered. */
truncated: boolean;
}
export interface ProjectSkillRepository {
projectId: string;
projectName: string;
projectVisibility: 'open' | 'private';
workspaceId: string;
repositoryUrl: string;
trackingRef: string;
bindingId: string | null;
sourceId: string | null;
status: 'available' | 'pending' | 'ready' | 'error';
importState?: 'queued' | 'importing';
lastError: string | null;
}
export interface SkillSourceDiscoveryRequest {
packageMode?: SkillSourcePackageMode;
repositoryUrl: string;
@@ -141,3 +176,13 @@ export interface SkillSourceRefreshResult {
unchanged: number;
warnings: string[];
}
export interface SkillSourceDisconnectRequest {
revision: number;
skills: 'copy' | 'delete';
}
export interface SkillSourceDisconnectResult {
id: string;
copiedCount: number;
deletedCount: number;
}
+1
View File
@@ -441,6 +441,7 @@ export {
resolveIssueRecoveryActionSchema,
isValidExistingBranchName,
retryWorkspaceExportSchema,
resumeWorkspaceFinalizationSchema,
issueReviewRequestSchema,
issueExecutionWorkspaceSettingsSchema,
checkoutIssueSchema,
@@ -53,6 +53,7 @@ export const instanceExperimentalSettingsSchema = z.object({
enableApps: z.boolean().default(true),
enablePublicMcp: z.boolean().default(false),
enableOpenAiDot: z.boolean().default(false),
enableGrokBot: z.boolean().default(false),
enableChatConnectors: z.boolean().default(false),
enableGitHubReviewBots: z.boolean().default(false),
// Compatibility only: old stored and managed values must still parse.
+8
View File
@@ -557,6 +557,14 @@ const RESOLVE_ISSUE_RECOVERY_ACTION_OUTCOMES = [
"cancelled",
] as const;
export const resumeWorkspaceFinalizationSchema = z.object({
actionId: z.string().guid(),
runId: z.string().guid(),
ownerToken: z.string().guid(),
controllerAndCopybackStopped: z.literal(true),
stopEvidence: z.string().trim().min(20).max(12000),
}).strict();
export const retryWorkspaceExportSchema = z.object({
actionId: z.string().guid(),
runId: z.string().guid(),
@@ -39,6 +39,15 @@ describe("plugin manifest validators", () => {
expect(pluginManifestV1Schema.parse({ ...manifest, capabilities: ["agents.lifecycle.manage"] }).agentLifecycle).toBe(true);
});
it("requires subscription authority and known resource types for durable delivery", () => {
const manifest = { id: "example.lifecycle", apiVersion: 1, version: "1.0.0", displayName: "Lifecycle", description: "Lifecycle tests", author: "Tests", categories: ["automation"], entrypoints: { worker: "worker.js" }, resourceLifecycle: ["project"] };
expect(pluginManifestV1Schema.safeParse({ ...manifest, capabilities: ["projects.read"] }).success).toBe(false);
expect(pluginManifestV1Schema.parse({ ...manifest, capabilities: ["events.subscribe"] }).resourceLifecycle).toEqual(["project"]);
for (const resourceLifecycle of [[], ["issue"]]) {
expect(pluginManifestV1Schema.safeParse({ ...manifest, resourceLifecycle, capabilities: ["events.subscribe"] }).success).toBe(false);
}
});
it("requires routing authority for native pooled connector declarations without a custom UI bundle", () => {
const manifest = { id: "example.pool", apiVersion: 1, version: "0.1.0", displayName: "Pool", description: "Pool", author: "Tests", categories: ["connector"], entrypoints: { worker: "worker.js" }, aiConnectionRouter: { name: "AI connection pool", description: "Use saved connections" } };
expect(pluginManifestV1Schema.safeParse({ ...manifest, capabilities: ["ui.page.register"] }).success).toBe(false);
+5
View File
@@ -783,6 +783,7 @@ export const pluginManifestV1Schema = z.object({
).optional(),
capabilities: z.array(z.enum(PLUGIN_CAPABILITIES)).min(1),
agentLifecycle: z.literal(true).optional(),
resourceLifecycle: z.array(z.enum(["agent", "project"])).min(1).max(2).optional(),
aiConnectionRouter: z.object({ name: z.string().trim().min(1).max(100), description: z.string().trim().min(1).max(500) }).strict().optional(),
entrypoints: z.object({
worker: z.string().min(1),
@@ -811,6 +812,10 @@ export const pluginManifestV1Schema = z.object({
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Agent lifecycle participation requires agents.lifecycle.manage", path: ["capabilities"] });
}
if (manifest.resourceLifecycle && !manifest.capabilities.includes("events.subscribe")) {
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Resource lifecycle delivery requires events.subscribe", path: ["capabilities"] });
}
// ── Entrypoint ↔ UI slot consistency ──────────────────────────────────
// Plugins that declare UI slots must also declare a UI entrypoint so the
// host knows where to load the bundle from (PLUGIN_SPEC.md §10.1).
@@ -25,3 +25,19 @@ export const skillSourceSelectionSchema = z.object({
excludedFolders: z.array(repoPath).max(10000),
connectionId: z.string().uuid().nullable().optional(),
});
export const skillSourceSettingsSchema = z.object({
revision: z.number().int().nonnegative(),
automaticUpdates: z.boolean().optional(),
importNewSkills: z.boolean().optional(),
connectionId: z.string().uuid().nullable().optional(),
trackingRef: z.string().trim().min(1).max(255).refine(value => !/[\x00-\x1f\x7f]/.test(value), 'Enter a valid branch or revision.').optional(),
});
export const skillSourceBranchesQuerySchema = z.object({ connectionId: z.string().uuid().optional() });
export const projectSkillSourceAdoptSchema = z.object({
workspaceId: z.string().uuid(),
});
export const skillSourceDisconnectSchema = z.object({
revision: z.number().int().nonnegative(),
skills: z.enum(['copy', 'delete']),
});