From 8d19f83b9467bd21d9a1fcd52e10ed372cfa2fbf Mon Sep 17 00:00:00 2001 From: Dotta Date: Tue, 3 Nov 2026 20:54:05 -0600 Subject: [PATCH] fix(agent): discover secure native secret entry before provider setup Co-Authored-By: Paperclip --- packages/shared/src/connection-intent-guidance.ts | 3 ++- server/src/routes/openapi.ts | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/shared/src/connection-intent-guidance.ts b/packages/shared/src/connection-intent-guidance.ts index 7178d4afddd..7ddd678e951 100644 --- a/packages/shared/src/connection-intent-guidance.ts +++ b/packages/shared/src/connection-intent-guidance.ts @@ -7,6 +7,7 @@ */ export const CONNECTION_INTENT_AGENT_GUIDANCE = [ "Connection tools:", + "- Storing a company or personal credential in Paperclip Secrets is distinct from connecting an external service. Use `search_api` to discover secret metadata and secure human-entry guidance; do not require an external provider choice merely to store an already-requested credential. Values stay in the human's secure entry surface, never in comments; personal values belong to the signed-in person.", "- When the user asks to connect a service, call `connections_search` before any service tool, even if already installed. Also search when a task needs a service and usable access is uncertain. Search by its name or capability and follow the returned `instruction`.", "- Distinguish tool, chat/channel, and AI account purposes. Use `search_api` and `call_api` for authorized connection configuration requested by the user. Standard agents can manage connections under their responsible user's authority; inspect the returned capabilities and respect explicit restrictions. Reuse saved accounts and setup drafts.", "- Suggest a relevant connection when it would help the task and usable access is missing. Respect recorded declines and provider choices. Ask only for missing decisions, consent, credentials, or authority; never ask for credentials in comments. Hand off the exact required human action at the saved setup link, then inspect status and continue through the API.", @@ -16,7 +17,7 @@ export const CONNECTION_INTENT_AGENT_GUIDANCE = [ "- Do not use connection tools for arbitrary MCP URLs or unrelated work.", ].join("\n"); -export const CONNECTIONS_SEARCH_TOOL_DESCRIPTION = "Search connections across tool, channel/email, and AI purposes with a service name or natural-language description (up to 4000 characters). Choose the relevant method and inspect its management capabilities. For user-requested configuration, discover and call the existing REST operation with search_api/call_api; reuse saved drafts and hand off only required human authentication or consent. Use connection_request for missing access or required user input. Also discovers verified external aggregator apps. Use first when asked to connect a service or usable access is uncertain; respect the returned instruction, recorded declines, and exact providerQuestion. Do not use for arbitrary MCP URLs. Search is read-only."; +export const CONNECTIONS_SEARCH_TOOL_DESCRIPTION = "Search connections across tool, channel/email, and AI purposes with a service name or natural-language description (up to 4000 characters). Storing a company or personal credential in Paperclip Secrets is a separate operation: use search_api for secret metadata and secure human-entry guidance, not external provider discovery. Choose the relevant method and inspect its management capabilities. For user-requested configuration, discover and call the existing REST operation with search_api/call_api; reuse saved drafts and hand off only required human authentication or consent. Use connection_request for missing access or required user input. Also discovers verified external aggregator apps. Use first when asked to connect a service or usable access is uncertain; respect the returned instruction, recorded declines, and exact providerQuestion. Do not use for arbitrary MCP URLs. Search is read-only."; export const CONNECTION_REQUEST_TOOL_DESCRIPTION = "Request the service identifier returned by connections_search. For an existing connection with missing agent access, pass connectionId and the exact required toolNames to create an embedded Grant access card; only the human can approve it. Follow the search instruction; aggregator routes require the saved provider-selection interaction ID. Only this tool creates the real setup card. If user action is needed, finish independent work, then yield without retrying or asking for credentials in comments."; diff --git a/server/src/routes/openapi.ts b/server/src/routes/openapi.ts index c41007ec0d4..08b58fd824c 100644 --- a/server/src/routes/openapi.ts +++ b/server/src/routes/openapi.ts @@ -5366,6 +5366,7 @@ registry.registerPath({ tags: ["secrets"], summary: "List secret metadata (id, name, key, status) — accessible to agents", + description: "Discover stored company credential metadata, never values. To add a company credential, direct the human to the company-prefixed /company/settings/secrets page and New secret, then bind only the intended agent's environment reference in its configuration. Secure storage of an already-requested key does not require connecting a new external service. Never request the value in chat or claim it is saved or bound before observing the authorized changes.", request: { params: z.object({ companyId: z.string() }) }, responses: { 200: r.ok(), 401: r.unauthorized, 403: r.forbidden }, }); @@ -5383,7 +5384,8 @@ registry.registerPath({ method: "post", path: "/api/companies/{companyId}/secrets", tags: ["secrets"], - summary: "Create a secret", + summary: "Create a company secret credential", + description: "Board-only secure credential entry. An agent cannot create the value on behalf of the human. Direct the human to New secret on the company-prefixed /company/settings/secrets page; never request the value in chat. After entry, use only the intended consumer's secret reference, not plaintext or a grant to every agent. A saved credential is not proof of runtime delivery.", request: { params: z.object({ companyId: z.string() }), body: jsonBody(createSecretSchema), @@ -5484,7 +5486,8 @@ registry.registerPath({ method: "get", path: "/api/companies/{companyId}/user-secret-definitions", tags: ["secrets"], - summary: "List user secret definitions", + summary: "List personal credential definitions (user secrets)", + description: "Board-only company-admin metadata for personal credential definitions, not their values. Agents cannot read this list using the human's identity. For an existing required personal credential, direct the current person to the company-prefixed /company/settings/secrets page, My secrets, then Set my value for the matching definition. The definition detail also supports ?definition= when its ID is already known. Only that signed-in person's value is set; another person's value cannot satisfy it. Never request values in chat. Storing a credential for an existing definition does not require choosing or connecting a new external provider.", request: { params: z.object({ companyId: z.string() }) }, responses: { 200: r.ok(), 401: r.unauthorized, 403: r.forbidden }, }); @@ -5560,7 +5563,8 @@ registry.registerPath({ method: "get", path: "/api/companies/{companyId}/me/user-secrets", tags: ["secrets"], - summary: "List my user secret values", + summary: "List my personal credential definitions and value metadata", + description: "Board-only current-person metadata. An agent cannot impersonate the person or list another owner's values. The signed-in person uses My secrets on the company-prefixed /company/settings/secrets page and Set my value for an existing definition; never send the value in chat. This secure entry is separate from connecting an external service. Confirm availability only after authorized entry and the intended task's required delivery have been observed.", request: { params: z.object({ companyId: z.string() }) }, responses: { 200: r.ok(), 401: r.unauthorized, 403: r.forbidden }, });