Files
PaperClipAI/ui/storybook/fixtures/connectionInstructions.ts
DottaandPaperclip 4857799a88 feat(connections): deliver saved instructions to authorized agent turns (#15216)
Persist optional connection instructions and deliver authorized snapshots to agent execution prompts. Keep provider templates with each app definition, preserve edits and opt-outs, and replace sessions when guidance or access changes.

Use shared production settings across setup and Permissions, with source visibility in agent Instructions. Add the initial memory-provider defaults and managed Honcho workspace configuration. Include migration 0298 and regression coverage for generic providers, runtime delivery, authorization, and catalog regeneration.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-05 18:16:00 -05:00

130 lines
10 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { defaultConnectionAgentInstructions, getConnectableAppDefinition, instanceExperimentalSettingsSchema } from "@paperclipai/shared";
import type { QueryClient } from "@tanstack/react-query";
import { queryKeys } from "@/lib/queryKeys";
import { storybookAgents } from "./paperclipData";
import inventory from "../../../doc/connections/memory-tool-inventory.json";
export const GUIDANCE_COMPANY = "company-storybook";
export const GUIDANCE_CONNECTION = "connection-guidance-preview";
export const guidanceAgents = storybookAgents.slice(0, 2).map((agent, i) => ({ ...agent, name: i ? "Morgan" : "Ada" }));
/** All connector requests remain in the story, including mutations from the real pages. */
export function installConnectionGuidanceFixtures(client: QueryClient, provider: "Honcho" | "Notion", options: { setup: boolean; askFirst: boolean; template?: string | null; disabled?: boolean; missingWorkspace?: boolean; failSave?: boolean }) {
const company = GUIDANCE_COMPANY;
const id = GUIDANCE_CONNECTION;
const app = structuredClone(getConnectableAppDefinition(provider.toLowerCase())!);
if (options.template === null) delete app.agentInstructions;
else if (options.template) app.agentInstructions = { id: `${app.slug}.storybook`, version: 1, text: options.template };
let failSave = options.failSave ?? false;
const timestamp = "2026-10-04T12:00:00.000Z";
let connected = !options.setup;
const connection = {
id, uid: id, companyId: company, applicationId: "guidance-app", name: provider,
connectionPurpose: "tool", credentialPolicy: "shared", transport: "mcp_remote",
authKind: provider === "Honcho" ? "api_key" : "oauth", status: "active", enabled: true,
healthStatus: "ok", healthMessage: null, requiresReauthorization: false,
createdByUserId: "user-storybook", createdByAgentId: null,
agentInstructions: app.agentInstructions ? { ...defaultConnectionAgentInstructions(app.agentInstructions)!, enabled: !options.disabled } : null,
config: { methodConfig: { workspaceId: options.missingWorkspace ? "" : "paperclip-acme" }, sourceTemplateKey: app.slug, connectionMethodKey: app.methods[0]?.key, url: app.methods[0]?.defaults?.serverUrl },
createdAt: timestamp, updatedAt: timestamp,
};
const application = { id: "guidance-app", companyId: company, applicationKey: app.slug, name: app.name, status: "active", metadata: { sourceTemplateKey: app.slug } };
const observed = provider === "Honcho" ? inventory.providers.find(p => p.provider === "honcho")!.tools : [
{ name: "search", risk: "read", parameters: ["query"] },
{ name: "fetch", risk: "read", parameters: ["id"] },
{ name: "create_page", risk: "write", parameters: [] },
];
const catalog = observed.map(tool => ({
id: `guidance-${tool.name}`, companyId: company, applicationId: application.id, connectionId: id,
entryKind: "tool", toolName: tool.name, title: tool.name.replaceAll("_", " "), description: `${provider} · ${tool.name}`,
inputSchema: { type: "object", properties: {} }, outputSchema: null, annotations: null,
riskLevel: tool.risk, isReadOnly: tool.risk === "read", isWrite: tool.risk === "write", isDestructive: tool.risk === "destructive",
status: "active", version: null, schemaHash: "storybook", addedAt: timestamp, createdAt: timestamp, updatedAt: timestamp,
}));
const capabilities = { canConfigure: true, canCreateOrganizationGrant: true, canSetCompanyInstall: true, canConnectAsCurrentUser: true, canManageAgentInstalls: true, canViewOtherPersonalIdentities: true };
let profile = { profileKey: `app:${id}`, entries: catalog.map(entry => ({ effect: "include", catalogEntryId: entry.id })), bindings: guidanceAgents.map(agent => ({ targetType: "agent", targetId: agent.id })) };
let policies = options.askFirst ? catalog.filter(t => !t.isReadOnly).map(t => ({ policyType: "require_approval", enabled: true, config: { source: "app_gallery_finish", connectionId: id, catalogEntryId: t.id } })) : [];
let installs: Array<{ targetType: string; targetId: string }> = [{ targetType: "company", targetId: company }];
const grants = { connection: { id, uid: id }, capabilities, currentUserId: "user-storybook", members: [], grants: [{
id: "guidance-grant", companyId: company, connectionId: id, kind: "organization", subjectUserId: null, subjectAgentId: null,
status: "active", isDefault: true, providerTenant: { name: "Acme" }, members: [], capabilities: { canRevoke: true, canEditAudience: true },
createdAt: timestamp, updatedAt: timestamp, credentialSecretRefs: [],
}] };
const gallery = { apps: [app], capabilities };
const settings = { ...instanceExperimentalSettingsSchema.parse({}), enableMemoryConnectors: true };
const directory = { users: [{ principalId: "user-storybook", status: "active", user: {
id: "user-storybook", email: "board@paperclip.local", name: "Riley Board", image: null,
} }] };
client.setQueryData(queryKeys.instance.experimentalSettings, settings);
const responses = new Map<string, () => unknown>([
[`/api/companies/${company}/user-directory`, () => directory],
[`/api/companies/${company}/agents`, () => guidanceAgents],
[`/api/companies/${company}/tools/gallery`, () => gallery],
[`/api/companies/${company}/tools/connections`, () => ({ connections: connected ? [connection] : [] })],
[`/api/companies/${company}/tools/applications`, () => ({ applications: connected ? [application] : [] })],
[`/api/tool-connections/${id}`, () => connection],
[`/api/tool-connections/${id}/catalog`, () => ({ catalog })],
[`/api/tool-connections/${id}/grants`, () => grants],
[`/api/tool-connections/${id}/installs`, () => ({ connectionId: id, installs })],
[`/api/companies/${company}/tools/profiles`, () => ({ profiles: [profile] })],
[`/api/companies/${company}/tools/policies`, () => ({ policies })],
]);
for (const agent of guidanceAgents) responses.set(`/api/companies/${company}/tools/profiles/effective/agents/${agent.id}`, () => ({ agentId: agent.id, profiles: [], entries: [], bindings: [], allowedTools: catalog, allowedToolNames: catalog.map(t => t.toolName), installedConnections: [connection] }));
const seeds: Array<[readonly unknown[], unknown]> = [
[queryKeys.access.companyUserDirectory(company), directory],
[queryKeys.agents.list(company), guidanceAgents], [queryKeys.apps.gallery(company), gallery],
[queryKeys.tools.connection(id), connection], [queryKeys.tools.catalog(id), { catalog }],
[queryKeys.tools.connectionGrants(id), grants], [queryKeys.tools.connectionInstalls(id), { connectionId: id, installs }],
[queryKeys.tools.connections(company), { connections: connected ? [connection] : [] }],
[queryKeys.tools.applications(company), { applications: connected ? [application] : [] }],
[queryKeys.tools.profiles(company), { profiles: [profile] }], [queryKeys.tools.policies(company), { policies }],
];
for (const [key, value] of seeds) client.setQueryData(key, value);
const original = window.fetch;
const fixture: typeof window.fetch = async (input, init) => {
const url = new URL(typeof input === "string" ? input : input instanceof URL ? input.href : input.url, location.origin);
const path = url.pathname;
const method = init?.method ?? (input instanceof Request ? input.method : "GET");
const body = () => JSON.parse(String(init?.body ?? "{}"));
if (path === "/api/instance/settings/experimental") return Response.json(settings);
if (path === `/api/companies/${company}/tools/apps/connect`) {
connected = true;
const next = body();
if (next.agentInstructions !== undefined) connection.agentInstructions = next.agentInstructions;
if (next.configValues !== undefined) connection.config.methodConfig = next.configValues;
return Response.json({ connectionId: id, connection, application, catalog,
actions: { readOnly: catalog.filter(t => t.isReadOnly), canMakeChanges: catalog.filter(t => !t.isReadOnly) }, suggestedDefaults: {}, auth: null });
}
if (path.endsWith(`/apps/${id}/finish`)) {
const next = body();
if (next.agentInstructions !== undefined) connection.agentInstructions = next.agentInstructions;
profile = { ...profile, entries: (next.enabledCatalogEntryIds ?? []).map((catalogEntryId: string) => ({ effect: "include", catalogEntryId })),
bindings: next.access === "all_agents" ? [{ targetType: "company", targetId: company }] : (next.access?.agentIds ?? []).map((targetId: string) => ({ targetType: "agent", targetId })) };
policies = (next.askFirstCatalogEntryIds ?? []).map((catalogEntryId: string) => ({ policyType: "require_approval", enabled: true, config: { source: "app_gallery_finish", connectionId: id, catalogEntryId } }));
return Response.json({ connectionId: id, installs });
}
if (path === `/api/tool-connections/${id}/installs` && method === "PUT") installs = body().installs ?? [];
if (path === `/api/tool-connections/${id}` && method === "PATCH") {
if (failSave) { failSave = false; return Response.json({ error: "Couldn’t save instructions. Your changes are still here. Try again." }, { status: 503 }); }
Object.assign(connection, body());
return Response.json(connection);
}
if (method === "GET" && responses.has(path)) return Response.json(responses.get(path)!());
if (path.endsWith("/preflight")) return Response.json({ oauth: { metadataFound: true, registrationAdvertised: true }, endpointReachable: true });
if (path.includes(`/tool-connections/${id}/`) || path.includes(`/tools/apps/${id}/`)) {
if (path.endsWith("/catalog/refresh")) return Response.json({ catalog, discoveredCount: catalog.length, quarantinedCount: 0 });
if (path.endsWith("/test-agents")) return Response.json({ agents: guidanceAgents });
// Unimplemented test/credential operations must not claim a real provider result.
if (path.includes("test-")) return Response.json({ error: "Provider execution is not included in this design preview." }, { status: 422 });
return Response.json({ connectionId: id, installs });
}
if (path.startsWith("/api/tools/oauth/")) return Response.json({ error: "Sign-in is not available in this design preview. No provider was contacted." }, { status: 422 });
if (path.startsWith(`/api/companies/${company}/tools/`) || path.startsWith("/api/tool-connections/") || path.startsWith("/api/tools/")) {
return Response.json({ error: "This connector operation is not included in this design preview." }, { status: 422 });
}
return original(input, init);
};
window.fetch = fixture;
return () => { if (window.fetch === fixture) window.fetch = original; };
}