mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-06 14:02:36 +01:00
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:
commit
b030683ca2
1176 files changed
+129461
-261897
No files matched your search
@@ -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[];
|
||||
@@ -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),
|
||||
);
|
||||
|
||||
@@ -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": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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.`;
|
||||
@@ -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:
|
||||
|
||||
@@ -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 }
|
||||
@@ -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],
|
||||
);
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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. */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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']),
|
||||
});
|
||||
Reference in new issue
Block a user