@tangle-network/agent-integrations 0.15.0 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,3 +1,25 @@
1
+ import {
2
+ INTEGRATION_FAMILIES,
3
+ assertValidIntegrationSpec,
4
+ buildHealthcheckPlan,
5
+ buildIntegrationCoverageConnectors,
6
+ consoleStepsToText,
7
+ getIntegrationFamily,
8
+ getIntegrationSpec,
9
+ integrationCoverageChecklistMarkdown,
10
+ integrationSpecToConnector,
11
+ listExecutableIntegrationSpecs,
12
+ listIntegrationCoverageSpecs,
13
+ listIntegrationSpecs,
14
+ renderAgentToolDescription,
15
+ renderConsoleSteps,
16
+ renderRunbookMarkdown,
17
+ specAuthToConnectorAuth,
18
+ validateCredentialFormat,
19
+ validateCredentialSet,
20
+ validateIntegrationSpec
21
+ } from "./chunk-DIJ3I66K.js";
22
+
1
23
  // src/index.ts
2
24
  import { createHmac as createHmac3, randomUUID as randomUUID3, timingSafeEqual as timingSafeEqual2 } from "crypto";
3
25
 
@@ -129,7 +151,7 @@ function buildActivepiecesConnectors(options = {}) {
129
151
  const category = override?.category ?? entry.category;
130
152
  const scopes = [`${entry.id}.read`, `${entry.id}.write`];
131
153
  const catalogActions = entry.actions.length > 0 ? entry.actions.map((action) => toAction(applyActionOverride(action, override), scopes, dataClassFor(category))) : defaultActions(entry.id, scopes, dataClassFor(category));
132
- const catalogTriggers = entry.triggers.map((trigger) => toTrigger(trigger, scopes, dataClassFor(category)));
154
+ const catalogTriggers = entry.triggers.map((trigger2) => toTrigger(trigger2, scopes, dataClassFor(category)));
133
155
  return {
134
156
  id: entry.id,
135
157
  providerId,
@@ -172,10 +194,10 @@ function toAction(action, scopes, dataClass) {
172
194
  inputSchema: { type: "object", additionalProperties: true, properties: {} }
173
195
  };
174
196
  }
175
- function toTrigger(trigger, scopes, dataClass) {
197
+ function toTrigger(trigger2, scopes, dataClass) {
176
198
  return {
177
- id: trigger.id,
178
- title: trigger.title,
199
+ id: trigger2.id,
200
+ title: trigger2.title,
179
201
  requiredScopes: [scopes[0]],
180
202
  dataClass,
181
203
  payloadSchema: { type: "object", additionalProperties: true, properties: {} }
@@ -210,867 +232,6 @@ function dataClassFor(category) {
210
232
  return "internal";
211
233
  }
212
234
 
213
- // src/coverage-catalog.ts
214
- var DEFAULT_PROVIDER_KINDS = ["first_party", "nango", "pipedream", "activepieces", "custom"];
215
- var COVERAGE_SPECS = [
216
- ["gmail", "Gmail", "email", "email", "tier_0", "email,google,workspace,inbox"],
217
- ["outlook-mail", "Outlook Mail", "email", "email", "tier_0", "email,microsoft,office,inbox"],
218
- ["google-calendar", "Google Calendar", "calendar", "calendar", "tier_0", "calendar,google,workspace,scheduling"],
219
- ["outlook-calendar", "Outlook Calendar", "calendar", "calendar", "tier_0", "calendar,microsoft,office,scheduling"],
220
- ["slack", "Slack", "chat", "chat", "tier_0", "chat,collaboration,internal-comms"],
221
- ["microsoft-teams", "Microsoft Teams", "chat", "chat", "tier_0", "chat,microsoft,collaboration"],
222
- ["google-drive", "Google Drive", "storage", "storage", "tier_0", "files,google,workspace,storage"],
223
- ["onedrive", "OneDrive", "storage", "storage", "tier_0", "files,microsoft,office,storage"],
224
- ["dropbox", "Dropbox", "storage", "storage", "tier_1", "files,storage"],
225
- ["box", "Box", "storage", "storage", "tier_1", "files,enterprise,storage"],
226
- ["google-docs", "Google Docs", "docs", "docs", "tier_0", "docs,google,workspace"],
227
- ["google-sheets", "Google Sheets", "database", "database", "tier_0", "sheets,spreadsheet,google,database"],
228
- ["microsoft-excel", "Microsoft Excel", "database", "database", "tier_0", "sheets,spreadsheet,microsoft,database"],
229
- ["notion", "Notion", "docs", "docs", "tier_0", "docs,wiki,knowledge"],
230
- ["airtable", "Airtable", "database", "database", "tier_0", "database,spreadsheet,ops"],
231
- ["coda", "Coda", "docs", "docs", "tier_1", "docs,wiki,ops"],
232
- ["confluence", "Confluence", "docs", "docs", "tier_1", "docs,wiki,atlassian"],
233
- ["sharepoint", "SharePoint", "storage", "storage", "tier_1", "files,microsoft,enterprise"],
234
- ["hubspot", "HubSpot", "crm", "crm", "tier_0", "crm,sales,marketing"],
235
- ["salesforce", "Salesforce", "crm", "crm", "tier_0", "crm,sales,enterprise"],
236
- ["pipedrive", "Pipedrive", "crm", "crm", "tier_1", "crm,sales"],
237
- ["zoho-crm", "Zoho CRM", "crm", "crm", "tier_1", "crm,sales"],
238
- ["close", "Close", "crm", "crm", "tier_1", "crm,sales"],
239
- ["attio", "Attio", "crm", "crm", "tier_1", "crm,sales,startups"],
240
- ["linear", "Linear", "workflow", "project", "tier_0", "project,engineering,tickets"],
241
- ["jira", "Jira", "workflow", "project", "tier_0", "project,engineering,tickets,atlassian"],
242
- ["github", "GitHub", "workflow", "dev", "tier_0", "code,dev,issues,git"],
243
- ["gitlab", "GitLab", "workflow", "dev", "tier_1", "code,dev,issues,git"],
244
- ["bitbucket", "Bitbucket", "workflow", "dev", "tier_2", "code,dev,git,atlassian"],
245
- ["asana", "Asana", "workflow", "project", "tier_1", "project,tasks"],
246
- ["trello", "Trello", "workflow", "project", "tier_1", "project,tasks,atlassian"],
247
- ["monday", "monday.com", "workflow", "project", "tier_1", "project,tasks,ops"],
248
- ["clickup", "ClickUp", "workflow", "project", "tier_1", "project,tasks,ops"],
249
- ["basecamp", "Basecamp", "workflow", "project", "tier_2", "project,tasks"],
250
- ["zendesk", "Zendesk", "crm", "support", "tier_0", "support,tickets,customer-success"],
251
- ["intercom", "Intercom", "crm", "support", "tier_0", "support,chat,customer-success"],
252
- ["freshdesk", "Freshdesk", "crm", "support", "tier_1", "support,tickets"],
253
- ["helpscout", "Help Scout", "crm", "support", "tier_1", "support,tickets"],
254
- ["front", "Front", "email", "support", "tier_1", "support,email,shared-inbox"],
255
- ["gorgias", "Gorgias", "crm", "support", "tier_1", "support,ecommerce"],
256
- ["stripe", "Stripe", "workflow", "finance", "tier_0", "payments,billing,finance"],
257
- ["quickbooks", "QuickBooks", "workflow", "finance", "tier_0", "accounting,finance"],
258
- ["xero", "Xero", "workflow", "finance", "tier_1", "accounting,finance"],
259
- ["netsuite", "NetSuite", "workflow", "finance", "tier_1", "erp,finance,enterprise"],
260
- ["sage", "Sage", "workflow", "finance", "tier_2", "accounting,finance"],
261
- ["plaid", "Plaid", "workflow", "finance", "tier_1", "banking,finance"],
262
- ["shopify", "Shopify", "workflow", "commerce", "tier_0", "ecommerce,orders,commerce"],
263
- ["woocommerce", "WooCommerce", "workflow", "commerce", "tier_1", "ecommerce,orders,wordpress"],
264
- ["bigcommerce", "BigCommerce", "workflow", "commerce", "tier_1", "ecommerce,orders"],
265
- ["amazon-seller-central", "Amazon Seller Central", "workflow", "commerce", "tier_1", "marketplace,ecommerce"],
266
- ["ebay", "eBay", "workflow", "commerce", "tier_2", "marketplace,ecommerce"],
267
- ["etsy", "Etsy", "workflow", "commerce", "tier_2", "marketplace,ecommerce"],
268
- ["mailchimp", "Mailchimp", "workflow", "marketing", "tier_0", "email-marketing,marketing"],
269
- ["klaviyo", "Klaviyo", "workflow", "marketing", "tier_0", "email-marketing,ecommerce,marketing"],
270
- ["marketo", "Marketo", "workflow", "marketing", "tier_1", "marketing,enterprise"],
271
- ["braze", "Braze", "workflow", "marketing", "tier_1", "marketing,lifecycle"],
272
- ["customer-io", "Customer.io", "workflow", "marketing", "tier_1", "marketing,lifecycle"],
273
- ["sendgrid", "SendGrid", "email", "email", "tier_1", "email,transactional"],
274
- ["postmark", "Postmark", "email", "email", "tier_1", "email,transactional"],
275
- ["twilio", "Twilio", "chat", "chat", "tier_0", "sms,voice,communications"],
276
- ["discord", "Discord", "chat", "chat", "tier_1", "chat,community"],
277
- ["telegram", "Telegram", "chat", "chat", "tier_1", "chat,community"],
278
- ["whatsapp-business", "WhatsApp Business", "chat", "chat", "tier_1", "chat,meta,customer-comms"],
279
- ["facebook-pages", "Facebook Pages", "workflow", "marketing", "tier_1", "social,meta,marketing"],
280
- ["instagram-business", "Instagram Business", "workflow", "marketing", "tier_1", "social,meta,marketing"],
281
- ["linkedin", "LinkedIn", "workflow", "sales", "tier_1", "social,sales,gtm"],
282
- ["x-twitter", "X / Twitter", "workflow", "marketing", "tier_1", "social,marketing"],
283
- ["youtube", "YouTube", "storage", "storage", "tier_1", "video,content"],
284
- ["tiktok", "TikTok", "workflow", "marketing", "tier_2", "social,video,marketing"],
285
- ["google-analytics", "Google Analytics", "database", "analytics", "tier_0", "analytics,web,marketing"],
286
- ["mixpanel", "Mixpanel", "database", "analytics", "tier_1", "analytics,product"],
287
- ["amplitude", "Amplitude", "database", "analytics", "tier_1", "analytics,product"],
288
- ["segment", "Segment", "database", "analytics", "tier_1", "analytics,cdp"],
289
- ["snowflake", "Snowflake", "database", "database", "tier_0", "warehouse,data"],
290
- ["bigquery", "BigQuery", "database", "database", "tier_0", "warehouse,google,data"],
291
- ["redshift", "Redshift", "database", "database", "tier_1", "warehouse,aws,data"],
292
- ["postgres", "Postgres", "database", "database", "tier_0", "database,sql"],
293
- ["mysql", "MySQL", "database", "database", "tier_1", "database,sql"],
294
- ["mongodb", "MongoDB", "database", "database", "tier_1", "database,nosql"],
295
- ["supabase", "Supabase", "database", "database", "tier_1", "database,postgres"],
296
- ["firebase", "Firebase", "database", "database", "tier_1", "database,google,app"],
297
- ["redis", "Redis", "database", "database", "tier_2", "database,cache"],
298
- ["aws-s3", "Amazon S3", "storage", "storage", "tier_0", "files,aws,storage"],
299
- ["aws-lambda", "AWS Lambda", "workflow", "dev", "tier_1", "aws,serverless,dev"],
300
- ["aws-cloudwatch", "AWS CloudWatch", "database", "analytics", "tier_1", "aws,logs,observability"],
301
- ["google-cloud-storage", "Google Cloud Storage", "storage", "storage", "tier_1", "files,gcp,storage"],
302
- ["azure-blob-storage", "Azure Blob Storage", "storage", "storage", "tier_1", "files,azure,storage"],
303
- ["vercel", "Vercel", "workflow", "dev", "tier_1", "deployments,dev"],
304
- ["netlify", "Netlify", "workflow", "dev", "tier_2", "deployments,dev"],
305
- ["cloudflare", "Cloudflare", "workflow", "dev", "tier_1", "edge,dev,dns"],
306
- ["sentry", "Sentry", "workflow", "dev", "tier_1", "errors,observability,dev"],
307
- ["datadog", "Datadog", "database", "analytics", "tier_1", "observability,logs,metrics"],
308
- ["new-relic", "New Relic", "database", "analytics", "tier_2", "observability,logs,metrics"],
309
- ["pagerduty", "PagerDuty", "workflow", "project", "tier_1", "incident,on-call"],
310
- ["opsgenie", "Opsgenie", "workflow", "project", "tier_2", "incident,on-call,atlassian"],
311
- ["okta", "Okta", "internal", "workflow", "tier_1", "identity,security"],
312
- ["auth0", "Auth0", "internal", "workflow", "tier_1", "identity,security"],
313
- ["workday", "Workday", "workflow", "hr", "tier_1", "hr,finance,enterprise"],
314
- ["bamboohr", "BambooHR", "workflow", "hr", "tier_1", "hr,people"],
315
- ["greenhouse", "Greenhouse", "workflow", "hr", "tier_1", "recruiting,hr"],
316
- ["lever", "Lever", "workflow", "hr", "tier_1", "recruiting,hr"],
317
- ["gusto", "Gusto", "workflow", "hr", "tier_1", "payroll,hr"],
318
- ["rippling", "Rippling", "workflow", "hr", "tier_1", "hr,it,identity"],
319
- ["docusign", "DocuSign", "docs", "docs", "tier_1", "contracts,signature,legal"],
320
- ["pandadoc", "PandaDoc", "docs", "docs", "tier_1", "contracts,signature,sales"],
321
- ["hellosign", "Dropbox Sign", "docs", "docs", "tier_2", "contracts,signature"],
322
- ["clio", "Clio", "workflow", "project", "tier_1", "legal,practice-management"],
323
- ["ironclad", "Ironclad", "docs", "docs", "tier_1", "legal,contracts"],
324
- ["lexisnexis", "LexisNexis", "docs", "docs", "tier_2", "legal,research"],
325
- ["calendly", "Calendly", "calendar", "calendar", "tier_0", "scheduling,calendar"],
326
- ["cal-com", "Cal.com", "calendar", "calendar", "tier_1", "scheduling,calendar"],
327
- ["zoom", "Zoom", "calendar", "calendar", "tier_0", "meetings,video,calendar"],
328
- ["google-meet", "Google Meet", "calendar", "calendar", "tier_1", "meetings,google,video"],
329
- ["microsoft-graph", "Microsoft Graph", "internal", "workflow", "tier_0", "microsoft,enterprise,identity"],
330
- ["openai", "OpenAI", "workflow", "ai", "tier_0", "ai,llm"],
331
- ["anthropic", "Anthropic", "workflow", "ai", "tier_1", "ai,llm"],
332
- ["gemini", "Google Gemini", "workflow", "ai", "tier_1", "ai,llm,google"],
333
- ["huggingface", "Hugging Face", "workflow", "ai", "tier_1", "ai,models"],
334
- ["pinecone", "Pinecone", "database", "database", "tier_1", "vector,database,ai"],
335
- ["weaviate", "Weaviate", "database", "database", "tier_1", "vector,database,ai"],
336
- ["qdrant", "Qdrant", "database", "database", "tier_1", "vector,database,ai"],
337
- ["zapier", "Zapier", "workflow", "workflow", "tier_1", "automation,workflow"],
338
- ["make", "Make", "workflow", "workflow", "tier_1", "automation,workflow"],
339
- ["nango", "Nango", "workflow", "workflow", "tier_1", "integration-platform,oauth"],
340
- ["pipedream", "Pipedream", "workflow", "workflow", "tier_1", "integration-platform,workflow"],
341
- ["activepieces", "Activepieces", "workflow", "workflow", "tier_1", "automation,workflow,open-source"],
342
- ["webhook", "Generic Webhook", "webhook", "webhook", "tier_0", "webhook,http,events", "none"],
343
- ["http", "HTTP Request", "workflow", "webhook", "tier_0", "http,api,webhook", "none"],
344
- ["rss", "RSS", "webhook", "webhook", "tier_1", "feeds,content", "none"],
345
- ["zapier-transfer", "Zapier Transfer", "workflow", "workflow", "long_tail", "automation,migration"],
346
- ["typeform", "Typeform", "workflow", "marketing", "tier_1", "forms,marketing"],
347
- ["google-forms", "Google Forms", "workflow", "marketing", "tier_1", "forms,google"],
348
- ["jotform", "Jotform", "workflow", "marketing", "tier_2", "forms"],
349
- ["webflow", "Webflow", "workflow", "marketing", "tier_1", "cms,website"],
350
- ["wordpress", "WordPress", "workflow", "marketing", "tier_1", "cms,website"],
351
- ["contentful", "Contentful", "docs", "docs", "tier_1", "cms,content"],
352
- ["sanity", "Sanity", "docs", "docs", "tier_1", "cms,content"],
353
- ["figma", "Figma", "docs", "docs", "tier_0", "design,creative"],
354
- ["canva", "Canva", "docs", "docs", "tier_1", "design,creative"],
355
- ["adobe-creative-cloud", "Adobe Creative Cloud", "storage", "storage", "tier_1", "design,creative,files"],
356
- ["miro", "Miro", "docs", "docs", "tier_1", "whiteboard,collaboration"],
357
- ["figjam", "FigJam", "docs", "docs", "tier_2", "whiteboard,design"]
358
- ];
359
- function listIntegrationCoverageSpecs() {
360
- return COVERAGE_SPECS.map(([id, title, category, actionPack2, priority, domains, auth = "oauth2"]) => ({
361
- id,
362
- title,
363
- category,
364
- actionPack: actionPack2,
365
- priority,
366
- auth,
367
- providerKinds: providerKindsFor(auth),
368
- domains: domains.split(",").map((domain) => domain.trim()).filter(Boolean),
369
- scopes: scopesFor(id, actionPack2)
370
- }));
371
- }
372
- function buildIntegrationCoverageConnectors(options = {}) {
373
- const providerId = options.providerId ?? "coverage";
374
- return listIntegrationCoverageSpecs().filter((spec) => !options.priorities || options.priorities.includes(spec.priority)).filter((spec) => !options.categories || options.categories.includes(spec.category)).filter((spec) => !options.actionPacks || options.actionPacks.includes(spec.actionPack)).map((spec) => specToConnector(spec, providerId));
375
- }
376
- function integrationCoverageChecklistMarkdown() {
377
- const specs = listIntegrationCoverageSpecs();
378
- const lines = [
379
- "# Agent Integrations Coverage Checklist",
380
- "",
381
- "Generated from `listIntegrationCoverageSpecs()`. Catalog presence means the product can plan/request/connect the integration; executable first-party adapters are promoted separately behind the same provider contract.",
382
- "",
383
- "## Summary",
384
- "",
385
- `- Total cataloged integrations: ${specs.length}`,
386
- `- Tier 0: ${specs.filter((spec) => spec.priority === "tier_0").length}`,
387
- `- Tier 1: ${specs.filter((spec) => spec.priority === "tier_1").length}`,
388
- `- Tier 2: ${specs.filter((spec) => spec.priority === "tier_2").length}`,
389
- `- Long tail: ${specs.filter((spec) => spec.priority === "long_tail").length}`,
390
- "",
391
- "## Checklist",
392
- ""
393
- ];
394
- for (const spec of specs) {
395
- lines.push(`- [ ] ${spec.priority} / ${spec.category} / ${spec.title} (${spec.id}) - ${spec.domains.join(", ")}`);
396
- }
397
- return `${lines.join("\n")}
398
- `;
399
- }
400
- function specToConnector(spec, providerId) {
401
- const actions = actionPack(spec.actionPack, spec.scopes ?? []);
402
- return {
403
- id: spec.id,
404
- providerId,
405
- title: spec.title,
406
- category: spec.category,
407
- auth: spec.auth,
408
- scopes: spec.scopes ?? [],
409
- actions,
410
- triggers: triggersFor(spec.actionPack, spec.scopes ?? []),
411
- metadata: {
412
- source: "coverage-catalog",
413
- priority: spec.priority,
414
- domains: spec.domains,
415
- providerKinds: spec.providerKinds,
416
- executable: false
417
- }
418
- };
419
- }
420
- function actionPack(pack, scopes) {
421
- const readScope = scopes.find((scope2) => scope2.endsWith(".read")) ?? scopes[0];
422
- const writeScope = scopes.find((scope2) => scope2.endsWith(".write")) ?? scopes[1] ?? readScope;
423
- const scope = (value) => value ? [value] : [];
424
- const read = (id, title, description) => ({
425
- id,
426
- title,
427
- description,
428
- risk: "read",
429
- requiredScopes: scope(readScope),
430
- dataClass: dataClassFor2(pack),
431
- inputSchema: objectSchema()
432
- });
433
- const write = (id, title, description) => ({
434
- id,
435
- title,
436
- description,
437
- risk: "write",
438
- requiredScopes: scope(writeScope),
439
- dataClass: dataClassFor2(pack),
440
- approvalRequired: true,
441
- inputSchema: objectSchema()
442
- });
443
- const destructive = (id, title, description) => ({
444
- id,
445
- title,
446
- description,
447
- risk: "destructive",
448
- requiredScopes: scope(writeScope),
449
- dataClass: dataClassFor2(pack),
450
- approvalRequired: true,
451
- inputSchema: objectSchema()
452
- });
453
- switch (pack) {
454
- case "email":
455
- return [read("messages.search", "Search messages", "Search messages and threads."), read("messages.read", "Read message", "Read a message by id."), write("drafts.create", "Create draft", "Create an email draft."), write("messages.send", "Send message", "Send or reply to an email message.")];
456
- case "calendar":
457
- return [read("events.search", "Search events", "Search calendar events."), read("availability.read", "Read availability", "Read availability windows."), write("events.create", "Create event", "Create a calendar event."), write("events.update", "Update event", "Update a calendar event."), destructive("events.cancel", "Cancel event", "Cancel a calendar event.")];
458
- case "chat":
459
- return [read("messages.search", "Search messages", "Search channel or direct messages."), read("channels.list", "List channels", "List channels or rooms."), write("messages.post", "Send message", "Send a message to a channel or direct message."), write("threads.reply", "Reply in thread", "Reply to a thread or conversation.")];
460
- case "crm":
461
- return [read("records.search", "Search records", "Search contacts, companies, and deals."), read("records.read", "Read record", "Read a CRM record."), write("records.upsert", "Upsert record", "Create or update a CRM record."), write("notes.create", "Create note", "Add a note or activity.")];
462
- case "storage":
463
- return [read("files.search", "Search files", "Search files and folders."), read("files.read", "Read file", "Read file metadata or content."), write("files.upload", "Upload file", "Upload a file."), write("files.update", "Update file", "Update file metadata or content.")];
464
- case "docs":
465
- return [read("documents.search", "Search documents", "Search documents or pages."), read("documents.read", "Read document", "Read a document."), write("documents.create", "Create document", "Create a document or page."), write("documents.update", "Update document", "Update a document or page.")];
466
- case "database":
467
- return [read("records.query", "Query records", "Query rows, records, or objects."), read("records.read", "Read record", "Read one row, record, or object."), write("records.upsert", "Upsert record", "Create or update a row, record, or object."), destructive("records.delete", "Delete record", "Delete a row, record, or object.")];
468
- case "project":
469
- return [read("tasks.search", "Search tasks", "Search tasks, tickets, or issues."), read("tasks.read", "Read task", "Read a task, ticket, or issue."), write("tasks.create", "Create task", "Create a task, ticket, or issue."), write("tasks.update", "Update task", "Update a task, ticket, or issue.")];
470
- case "support":
471
- return [read("tickets.search", "Search tickets", "Search support tickets or conversations."), read("customers.read", "Read customer", "Read a customer profile."), write("tickets.reply", "Reply to ticket", "Reply to a support ticket."), write("tickets.update", "Update ticket", "Update ticket status, tags, or assignee.")];
472
- case "marketing":
473
- return [read("contacts.search", "Search contacts", "Search marketing contacts or audiences."), read("campaigns.read", "Read campaign", "Read campaign metadata and performance."), write("contacts.upsert", "Upsert contact", "Create or update a contact."), write("campaigns.create", "Create campaign", "Create a campaign draft.")];
474
- case "sales":
475
- return [read("prospects.search", "Search prospects", "Search prospects, leads, or accounts."), read("activities.read", "Read activities", "Read sales activity history."), write("prospects.upsert", "Upsert prospect", "Create or update a prospect."), write("sequence.enqueue", "Enroll in sequence", "Enroll a prospect in a sales sequence.")];
476
- case "commerce":
477
- return [read("orders.search", "Search orders", "Search orders."), read("customers.read", "Read customer", "Read customer and purchase history."), write("orders.update", "Update order", "Update order metadata or fulfillment state."), write("products.update", "Update product", "Update product metadata.")];
478
- case "finance":
479
- return [read("transactions.search", "Search transactions", "Search transactions, invoices, or payments."), read("accounts.read", "Read account", "Read account or customer financial record."), write("invoices.create", "Create invoice", "Create an invoice or payment object."), write("records.sync", "Sync record", "Sync a finance or accounting record.")];
480
- case "hr":
481
- return [read("people.search", "Search people", "Search employees, candidates, or contractors."), read("people.read", "Read person", "Read a person profile."), write("people.update", "Update person", "Update a person profile."), write("events.create", "Create HR event", "Create a recruiting or HR event.")];
482
- case "dev":
483
- return [read("resources.search", "Search resources", "Search issues, repos, deployments, logs, or incidents."), read("resources.read", "Read resource", "Read a developer resource."), write("resources.create", "Create resource", "Create an issue, deployment, incident, or config."), write("resources.update", "Update resource", "Update a developer resource.")];
484
- case "ai":
485
- return [read("models.list", "List models", "List available models or endpoints."), write("responses.create", "Create response", "Create an AI response or job."), write("embeddings.create", "Create embeddings", "Create embeddings or vector jobs."), read("usage.read", "Read usage", "Read usage metadata.")];
486
- case "analytics":
487
- return [read("reports.query", "Query reports", "Query analytics reports."), read("events.search", "Search events", "Search analytics events."), write("events.track", "Track event", "Track an analytics event."), write("audiences.sync", "Sync audience", "Sync an audience or cohort.")];
488
- case "workflow":
489
- return [read("runs.search", "Search runs", "Search workflow runs or jobs."), read("templates.list", "List templates", "List workflow templates."), write("runs.start", "Start run", "Start a workflow run."), write("webhooks.dispatch", "Dispatch webhook", "Dispatch a workflow webhook.")];
490
- case "webhook":
491
- return [write("requests.send", "Send request", "Send an HTTP request or webhook event."), read("events.search", "Search events", "Search received webhook events."), write("subscriptions.create", "Create subscription", "Create a webhook subscription."), destructive("subscriptions.delete", "Delete subscription", "Delete a webhook subscription.")];
492
- }
493
- }
494
- function triggersFor(pack, scopes) {
495
- const readScope = scopes.find((scope) => scope.endsWith(".read")) ?? scopes[0];
496
- const requiredScopes2 = readScope ? [readScope] : [];
497
- if (pack === "email") return [{ id: "message.received", title: "Message received", requiredScopes: requiredScopes2, dataClass: "private" }];
498
- if (pack === "calendar") return [{ id: "event.changed", title: "Event changed", requiredScopes: requiredScopes2, dataClass: "private" }];
499
- if (pack === "chat") return [{ id: "message.posted", title: "Message posted", requiredScopes: requiredScopes2, dataClass: "private" }];
500
- if (pack === "crm") return [{ id: "record.changed", title: "Record changed", requiredScopes: requiredScopes2, dataClass: "private" }];
501
- if (pack === "support") return [{ id: "ticket.changed", title: "Ticket changed", requiredScopes: requiredScopes2, dataClass: "private" }];
502
- if (pack === "commerce") return [{ id: "order.changed", title: "Order changed", requiredScopes: requiredScopes2, dataClass: "sensitive" }];
503
- if (pack === "finance") return [{ id: "transaction.changed", title: "Transaction changed", requiredScopes: requiredScopes2, dataClass: "sensitive" }];
504
- if (pack === "workflow" || pack === "webhook") return [{ id: "event.received", title: "Event received", requiredScopes: requiredScopes2, dataClass: "internal" }];
505
- return void 0;
506
- }
507
- function scopesFor(id, pack) {
508
- if (pack === "webhook") return [];
509
- return [`${id}.read`, `${id}.write`];
510
- }
511
- function providerKindsFor(auth) {
512
- if (auth === "none") return ["first_party", "pipedream", "activepieces", "custom"];
513
- return DEFAULT_PROVIDER_KINDS;
514
- }
515
- function dataClassFor2(pack) {
516
- if (pack === "finance" || pack === "commerce" || pack === "hr") return "sensitive";
517
- if (pack === "workflow" || pack === "webhook" || pack === "dev" || pack === "analytics") return "internal";
518
- return "private";
519
- }
520
- function objectSchema() {
521
- return { type: "object", additionalProperties: true, properties: {} };
522
- }
523
-
524
- // src/specs/families.ts
525
- var INTEGRATION_FAMILIES = {
526
- google: {
527
- id: "google",
528
- title: "Google OAuth",
529
- authMode: "oauth2",
530
- consoleUrl: "https://console.cloud.google.com/apis/credentials",
531
- authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth",
532
- tokenUrl: "https://oauth2.googleapis.com/token",
533
- redirectUriTemplate: "https://{host}/api/integrations/oauth/google/callback",
534
- credentialFields: [
535
- { label: "Client ID", env: "GOOGLE_OAUTH_CLIENT_ID", description: "Google OAuth client ID.", example: "1234567890-abc.apps.googleusercontent.com", regex: "^[0-9]+-[a-zA-Z0-9_-]+\\.apps\\.googleusercontent\\.com$", secret: false },
536
- { label: "Client Secret", env: "GOOGLE_OAUTH_CLIENT_SECRET", description: "Google OAuth client secret.", example: "GOCSPX-...", secret: true }
537
- ],
538
- consoleSteps: [
539
- { id: "project", title: "Select project", detail: "Open Google Cloud Console and select the project that owns the OAuth client." },
540
- { id: "consent", title: "Configure consent screen", detail: "Configure OAuth consent, app name, support email, and publishing status appropriate for the deployment." },
541
- { id: "client", title: "Create web client", detail: "Create an OAuth client of type Web application." },
542
- { id: "redirect", title: "Add redirect URI", detail: "Add {redirectUri} as an authorized redirect URI.", copyValue: "{redirectUri}" },
543
- { id: "scopes", title: "Add scopes", detail: "Add the provider scopes listed in this spec." }
544
- ],
545
- knownQuirks: [
546
- { id: "offline-access", severity: "warning", message: "Use access_type=offline and prompt=consent when refresh tokens are required." },
547
- { id: "verification", severity: "warning", message: "Sensitive or restricted scopes may require Google verification before broad external use." }
548
- ],
549
- lifecycle: { supportsRefresh: true, supportsRevoke: true, supportsIncrementalAuth: true, recommendedHealthcheckIntervalHours: 24 }
550
- },
551
- "microsoft-graph": {
552
- id: "microsoft-graph",
553
- title: "Microsoft Graph OAuth",
554
- authMode: "oauth2",
555
- consoleUrl: "https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade",
556
- authorizationUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
557
- tokenUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
558
- redirectUriTemplate: "https://{host}/api/integrations/oauth/microsoft/callback",
559
- credentialFields: [
560
- { label: "Client ID", env: "MS_OAUTH_CLIENT_ID", description: "Microsoft Entra application client ID.", example: "00000000-0000-0000-0000-000000000000", regex: "^[0-9a-fA-F-]{36}$", secret: false },
561
- { label: "Client Secret", env: "MS_OAUTH_CLIENT_SECRET", description: "Microsoft Entra client secret value.", secret: true }
562
- ],
563
- consoleSteps: [
564
- { id: "app", title: "Register app", detail: "Create or open an app registration in Microsoft Entra." },
565
- { id: "redirect", title: "Add redirect URI", detail: "Add {redirectUri} as a Web redirect URI.", copyValue: "{redirectUri}" },
566
- { id: "secret", title: "Create secret", detail: "Create a client secret and store the secret value, not the secret ID." },
567
- { id: "permissions", title: "Add Graph permissions", detail: "Add the delegated Graph scopes listed in this spec and grant admin consent where required." }
568
- ],
569
- knownQuirks: [
570
- { id: "tenant-common", severity: "info", message: "The common tenant supports multi-tenant OAuth; single-tenant deployments should override the tenant segment." },
571
- { id: "admin-consent", severity: "warning", message: "Some Graph scopes require tenant admin consent." }
572
- ],
573
- lifecycle: { supportsRefresh: true, supportsRevoke: true, supportsIncrementalAuth: true, recommendedHealthcheckIntervalHours: 24 }
574
- },
575
- atlassian: {
576
- id: "atlassian",
577
- title: "Atlassian OAuth",
578
- authMode: "oauth2",
579
- consoleUrl: "https://developer.atlassian.com/console/myapps/",
580
- authorizationUrl: "https://auth.atlassian.com/authorize",
581
- tokenUrl: "https://auth.atlassian.com/oauth/token",
582
- redirectUriTemplate: "https://{host}/api/integrations/oauth/atlassian/callback",
583
- credentialFields: [
584
- { label: "Client ID", description: "Atlassian OAuth client ID.", secret: false },
585
- { label: "Client Secret", description: "Atlassian OAuth client secret.", secret: true }
586
- ],
587
- consoleSteps: [
588
- { id: "app", title: "Create OAuth app", detail: "Create an OAuth 2.0 app in the Atlassian developer console." },
589
- { id: "redirect", title: "Add callback URL", detail: "Add {redirectUri} as the callback URL.", copyValue: "{redirectUri}" },
590
- { id: "apis", title: "Enable APIs", detail: "Enable the Jira or Confluence APIs required by this connector." }
591
- ],
592
- lifecycle: { supportsRefresh: true, supportsRevoke: false, supportsIncrementalAuth: false, recommendedHealthcheckIntervalHours: 24 }
593
- },
594
- salesforce: {
595
- id: "salesforce",
596
- title: "Salesforce OAuth",
597
- authMode: "oauth2",
598
- consoleUrl: "https://login.salesforce.com",
599
- authorizationUrl: "https://login.salesforce.com/services/oauth2/authorize",
600
- tokenUrl: "https://login.salesforce.com/services/oauth2/token",
601
- redirectUriTemplate: "https://{host}/api/integrations/oauth/salesforce/callback",
602
- credentialFields: [
603
- { label: "Client ID", env: "SALESFORCE_OAUTH_CLIENT_ID", description: "Salesforce connected app consumer key.", secret: false },
604
- { label: "Client Secret", env: "SALESFORCE_OAUTH_CLIENT_SECRET", description: "Salesforce connected app consumer secret.", secret: true }
605
- ],
606
- consoleSteps: [
607
- { id: "connected-app", title: "Create connected app", detail: "Create a Salesforce connected app with OAuth enabled." },
608
- { id: "callback", title: "Add callback URL", detail: "Add {redirectUri} as the callback URL.", copyValue: "{redirectUri}" },
609
- { id: "scopes", title: "Select scopes", detail: "Add api and refresh_token/offline_access, plus any connector-specific scopes." }
610
- ],
611
- knownQuirks: [
612
- { id: "instance-url", severity: "critical", message: "Runtime calls must use the instance_url returned by the token response." }
613
- ],
614
- lifecycle: { supportsRefresh: true, supportsRevoke: true, supportsIncrementalAuth: false, recommendedHealthcheckIntervalHours: 24 }
615
- },
616
- hubspot: {
617
- id: "hubspot",
618
- title: "HubSpot OAuth",
619
- authMode: "oauth2",
620
- consoleUrl: "https://developers.hubspot.com/",
621
- authorizationUrl: "https://app.hubspot.com/oauth/authorize",
622
- tokenUrl: "https://api.hubapi.com/oauth/v1/token",
623
- redirectUriTemplate: "https://{host}/api/integrations/oauth/hubspot/callback",
624
- credentialFields: [
625
- { label: "Client ID", env: "HUBSPOT_OAUTH_CLIENT_ID", description: "HubSpot app client ID.", secret: false },
626
- { label: "Client Secret", env: "HUBSPOT_OAUTH_CLIENT_SECRET", description: "HubSpot app client secret.", secret: true }
627
- ],
628
- consoleSteps: [
629
- { id: "app", title: "Create private/public app", detail: "Create a HubSpot app and configure OAuth." },
630
- { id: "redirect", title: "Add redirect URL", detail: "Add {redirectUri} to the app redirect URLs.", copyValue: "{redirectUri}" },
631
- { id: "scopes", title: "Add CRM scopes", detail: "Add the CRM object scopes listed in this spec." }
632
- ],
633
- lifecycle: { supportsRefresh: true, supportsRevoke: true, supportsIncrementalAuth: false, recommendedHealthcheckIntervalHours: 24 }
634
- },
635
- slack: {
636
- id: "slack",
637
- title: "Slack OAuth",
638
- authMode: "oauth2",
639
- consoleUrl: "https://api.slack.com/apps",
640
- authorizationUrl: "https://slack.com/oauth/v2/authorize",
641
- tokenUrl: "https://slack.com/api/oauth.v2.access",
642
- redirectUriTemplate: "https://{host}/api/integrations/oauth/slack/callback",
643
- credentialFields: [
644
- { label: "Client ID", env: "SLACK_OAUTH_CLIENT_ID", description: "Slack app client ID.", secret: false },
645
- { label: "Client Secret", env: "SLACK_OAUTH_CLIENT_SECRET", description: "Slack app client secret.", secret: true }
646
- ],
647
- consoleSteps: [
648
- { id: "app", title: "Create Slack app", detail: "Create or open a Slack app." },
649
- { id: "redirect", title: "Add redirect URL", detail: "Add {redirectUri} under OAuth & Permissions.", copyValue: "{redirectUri}" },
650
- { id: "scopes", title: "Add bot scopes", detail: "Add the bot token scopes listed in this spec and reinstall the app." }
651
- ],
652
- knownQuirks: [
653
- { id: "bot-token", severity: "info", message: "Slack usually returns a bot access token; refresh tokens require token rotation." }
654
- ],
655
- lifecycle: { supportsRefresh: false, supportsRevoke: true, supportsIncrementalAuth: false, recommendedHealthcheckIntervalHours: 24 }
656
- },
657
- notion: {
658
- id: "notion",
659
- title: "Notion OAuth",
660
- authMode: "oauth2",
661
- consoleUrl: "https://www.notion.so/my-integrations",
662
- authorizationUrl: "https://api.notion.com/v1/oauth/authorize",
663
- tokenUrl: "https://api.notion.com/v1/oauth/token",
664
- redirectUriTemplate: "https://{host}/api/integrations/oauth/notion/callback",
665
- credentialFields: [
666
- { label: "Client ID", env: "NOTION_OAUTH_CLIENT_ID", description: "Notion integration OAuth client ID.", secret: false },
667
- { label: "Client Secret", env: "NOTION_OAUTH_CLIENT_SECRET", description: "Notion integration OAuth client secret.", secret: true }
668
- ],
669
- consoleSteps: [
670
- { id: "integration", title: "Create integration", detail: "Create a Notion public integration." },
671
- { id: "redirect", title: "Add redirect URI", detail: "Add {redirectUri} as the redirect URI.", copyValue: "{redirectUri}" },
672
- { id: "capabilities", title: "Select capabilities", detail: "Enable read/update/insert capabilities matching this connector." }
673
- ],
674
- lifecycle: { supportsRefresh: true, supportsRevoke: true, supportsIncrementalAuth: false, recommendedHealthcheckIntervalHours: 24 }
675
- },
676
- "standard-oauth2": {
677
- id: "standard-oauth2",
678
- title: "Standard OAuth 2.0",
679
- authMode: "oauth2",
680
- redirectUriTemplate: "https://{host}/api/integrations/oauth/{kind}/callback",
681
- credentialFields: [
682
- { label: "Client ID", description: "OAuth client ID.", secret: false },
683
- { label: "Client Secret", description: "OAuth client secret.", secret: true }
684
- ],
685
- consoleSteps: [
686
- { id: "app", title: "Create OAuth app", detail: "Create an OAuth app in the provider console." },
687
- { id: "redirect", title: "Add redirect URI", detail: "Add {redirectUri} as an allowed redirect URI.", copyValue: "{redirectUri}" },
688
- { id: "scopes", title: "Add scopes", detail: "Add the scopes listed in this spec." }
689
- ],
690
- lifecycle: { supportsRefresh: true, supportsRevoke: false, supportsIncrementalAuth: false, recommendedHealthcheckIntervalHours: 24 }
691
- },
692
- "api-key": {
693
- id: "api-key",
694
- title: "API key",
695
- authMode: "api_key",
696
- credentialFields: [
697
- { label: "API Key", description: "Provider API key or token.", example: "sk_...", secret: true }
698
- ],
699
- consoleSteps: [
700
- { id: "token", title: "Create token", detail: "Create an API key/token in the provider console with the minimum required permissions." }
701
- ],
702
- lifecycle: { supportsRefresh: false, supportsRevoke: true, supportsIncrementalAuth: false, recommendedHealthcheckIntervalHours: 24 }
703
- },
704
- hmac: {
705
- id: "hmac",
706
- title: "HMAC secret",
707
- authMode: "hmac",
708
- credentialFields: [
709
- { label: "Signing Secret", description: "Webhook signing secret.", secret: true }
710
- ],
711
- consoleSteps: [
712
- { id: "secret", title: "Configure signing secret", detail: "Configure the shared signing secret in the sender and receiver." }
713
- ],
714
- lifecycle: { supportsRefresh: false, supportsRevoke: true, supportsIncrementalAuth: false, recommendedHealthcheckIntervalHours: 24 }
715
- },
716
- none: {
717
- id: "none",
718
- title: "No authentication",
719
- authMode: "none",
720
- credentialFields: [],
721
- consoleSteps: [
722
- { id: "configure", title: "Configure endpoint", detail: "No provider credentials are required." }
723
- ],
724
- lifecycle: { supportsRefresh: false, supportsRevoke: false, supportsIncrementalAuth: false }
725
- }
726
- };
727
- function getIntegrationFamily(id) {
728
- return INTEGRATION_FAMILIES[id];
729
- }
730
-
731
- // src/specs/overrides.ts
732
- var INTEGRATION_OVERRIDES = {
733
- // ── Stripe pack ────────────────────────────────────────────────────
734
- // Stripe issues two key types: secret keys (sk_*) and restricted keys
735
- // (rk_*). For voice-agent workloads, restricted keys are the right call
736
- // — least-privilege scoped to the specific resources the agent can
737
- // touch. The hint nudges operators toward that path.
738
- "stripe-pack": {
739
- consoleUrl: "https://dashboard.stripe.com/apikeys",
740
- credentialFields: [
741
- {
742
- label: "Stripe secret key",
743
- description: "Restricted key recommended. Dashboard \u2192 Developers \u2192 API keys \u2192 Create restricted key. Grant write access on Customers, Invoices, and Checkout Sessions.",
744
- example: "sk_live_\u2026 or rk_live_\u2026 (use sk_test_\u2026 / rk_test_\u2026 for staging)",
745
- regex: "^(sk|rk)_(live|test)_[A-Za-z0-9]+$",
746
- secret: true
747
- }
748
- ],
749
- consoleSteps: [
750
- {
751
- id: "open-keys",
752
- title: "Open Stripe API keys",
753
- detail: "Visit https://dashboard.stripe.com/apikeys",
754
- copyValue: "https://dashboard.stripe.com/apikeys"
755
- },
756
- {
757
- id: "create-restricted",
758
- title: "Create a restricted key",
759
- detail: 'Click "Create restricted key". Name it something descriptive (e.g. "ph0ny voice agent \u2014 prod"). Grant WRITE on Customers, Invoices, and Checkout Sessions. Leave everything else NONE.'
760
- },
761
- {
762
- id: "paste",
763
- title: "Paste the key",
764
- detail: "Copy the key Stripe shows once (rk_live_\u2026 or sk_live_\u2026). Paste it into ph0ny. The key is sealed before persistence."
765
- }
766
- ]
767
- },
768
- // ── Twilio SMS ─────────────────────────────────────────────────────
769
- // Twilio's REST API uses Basic auth with two parts: Account SID
770
- // (public-ish, AC…) + Auth Token (secret). The default api-key family
771
- // only exposes one field, which doesn't fit. Providing both fields
772
- // explicitly lets the consumer's UI render two inputs.
773
- "twilio-sms": {
774
- consoleUrl: "https://console.twilio.com/",
775
- credentialFields: [
776
- {
777
- label: "Account SID",
778
- description: "Your Twilio Account SID. Console \u2192 Account \u2192 API keys & tokens.",
779
- example: "AC\u2026 (34 hex chars)",
780
- regex: "^AC[a-f0-9]{32}$",
781
- secret: false
782
- },
783
- {
784
- label: "Auth Token",
785
- description: "Your Twilio Auth Token (or Standard API Key secret). Use a non-primary auth token in production so rotating it won't break other Twilio integrations.",
786
- secret: true
787
- }
788
- ],
789
- consoleSteps: [
790
- {
791
- id: "open",
792
- title: "Open Twilio console",
793
- detail: "Visit https://console.twilio.com/",
794
- copyValue: "https://console.twilio.com/"
795
- },
796
- {
797
- id: "find",
798
- title: "Find your Account SID + Auth Token",
799
- detail: "Account info is on the dashboard home. For better security, create a Standard API Key (Account \u2192 API keys & tokens \u2192 Create API Key) and use the SID + Secret pair instead of the primary auth token."
800
- },
801
- {
802
- id: "paste",
803
- title: "Paste both values",
804
- detail: "Account SID is non-secret; Auth Token is sealed before persistence."
805
- }
806
- ],
807
- knownQuirks: [
808
- {
809
- id: "subaccount-tokens",
810
- severity: "info",
811
- message: "If you use Twilio subaccounts, paste the SID/Token of the subaccount that owns the phone numbers your agent calls \u2014 not the master account."
812
- }
813
- ]
814
- }
815
- };
816
- function getIntegrationOverride(kind) {
817
- return INTEGRATION_OVERRIDES[kind];
818
- }
819
-
820
- // src/specs/registry.ts
821
- var EXECUTABLE_KINDS = /* @__PURE__ */ new Set([
822
- "google-calendar",
823
- "google-sheets",
824
- "outlook-calendar",
825
- "microsoft-calendar",
826
- "slack",
827
- "hubspot",
828
- "notion",
829
- "notion-database",
830
- "salesforce",
831
- "github",
832
- "gitlab",
833
- "airtable",
834
- "asana",
835
- "stripe",
836
- "stripe-pack",
837
- "twilio",
838
- "twilio-sms",
839
- "webhook"
840
- ]);
841
- var KIND_ALIASES = {
842
- "outlook-calendar": "microsoft-calendar",
843
- notion: "notion-database",
844
- stripe: "stripe-pack",
845
- twilio: "twilio-sms"
846
- };
847
- function listIntegrationSpecs() {
848
- const connectors = new Map(buildIntegrationCoverageConnectors({ providerId: "spec" }).map((c) => [c.id, c]));
849
- return listIntegrationCoverageSpecs().map((coverage) => {
850
- const connector = connectors.get(coverage.id);
851
- if (!connector) throw new Error(`missing coverage connector for ${coverage.id}`);
852
- return specFromCoverage(coverage, connector);
853
- });
854
- }
855
- function getIntegrationSpec(kind) {
856
- const canonical = KIND_ALIASES[kind] ?? kind;
857
- return listIntegrationSpecs().find((spec) => spec.kind === canonical || KIND_ALIASES[spec.kind] === canonical);
858
- }
859
- function listExecutableIntegrationSpecs() {
860
- return listIntegrationSpecs().filter((spec) => spec.status === "executable");
861
- }
862
- function integrationSpecToConnector(spec, providerId = "spec") {
863
- return {
864
- id: spec.kind,
865
- providerId,
866
- title: spec.title,
867
- category: spec.category,
868
- auth: spec.auth.mode === "api_key" ? "api_key" : spec.auth.mode === "oauth2" ? "oauth2" : spec.auth.mode === "none" ? "none" : "custom",
869
- scopes: spec.permissions.flatMap((permission) => permission.providerScopes),
870
- actions: spec.actions,
871
- triggers: spec.triggers,
872
- metadata: {
873
- ...spec.metadata ?? {},
874
- source: "integration-spec",
875
- status: spec.status,
876
- family: spec.family,
877
- plannerHints: spec.plannerHints
878
- }
879
- };
880
- }
881
- function specFromCoverage(coverage, connector) {
882
- const kind = KIND_ALIASES[coverage.id] ?? coverage.id;
883
- const family = familyFor(coverage);
884
- const familySpec = getIntegrationFamily(family);
885
- const permissions = permissionsFor(coverage, connector.actions);
886
- const auth = authFor(coverage, family, permissions);
887
- const status = statusFor(kind);
888
- const override = getIntegrationOverride(kind) ?? getIntegrationOverride(coverage.id);
889
- const knownQuirks = override?.knownQuirks ? [...familySpec.knownQuirks ?? [], ...override.knownQuirks] : familySpec.knownQuirks;
890
- return {
891
- kind,
892
- title: connector.title,
893
- category: connector.category,
894
- status,
895
- family,
896
- auth,
897
- permissions,
898
- actions: connector.actions,
899
- triggers: connector.triggers,
900
- setup: {
901
- consoleUrl: override?.consoleUrl ?? familySpec.consoleUrl,
902
- consoleSteps: override?.consoleSteps ?? familySpec.consoleSteps,
903
- credentialFields: override?.credentialFields ?? credentialFieldsFor(auth),
904
- redirectUriTemplate: auth.mode === "oauth2" ? auth.redirectUriTemplate : familySpec.redirectUriTemplate,
905
- knownQuirks,
906
- postSetup: override?.postSetup,
907
- healthcheck: override?.healthcheck ?? healthcheckFor(kind, status, auth)
908
- },
909
- lifecycle: familySpec.lifecycle,
910
- plannerHints: plannerHintsFor(coverage, connector.actions),
911
- metadata: { priority: coverage.priority, domains: coverage.domains }
912
- };
913
- }
914
- function familyFor(spec) {
915
- if (hmacKinds.has(spec.id)) return "hmac";
916
- if (spec.auth === "none") return "none";
917
- if (spec.id.startsWith("google-") || spec.domains.includes("google")) return "google";
918
- if (spec.id.startsWith("microsoft-") || ["outlook-mail", "outlook-calendar", "onedrive", "sharepoint"].includes(spec.id)) return "microsoft-graph";
919
- if (["jira", "confluence", "trello", "bitbucket"].includes(spec.id)) return "atlassian";
920
- if (spec.id === "salesforce") return "salesforce";
921
- if (spec.id === "hubspot") return "hubspot";
922
- if (spec.id === "slack") return "slack";
923
- if (spec.id === "notion") return "notion";
924
- if (apiKeyKinds.has(spec.id)) return "api-key";
925
- return "standard-oauth2";
926
- }
927
- var apiKeyKinds = /* @__PURE__ */ new Set(["github", "gitlab", "airtable", "asana", "stripe", "twilio", "sendgrid", "postmark"]);
928
- var hmacKinds = /* @__PURE__ */ new Set(["webhook"]);
929
- function authFor(spec, family, permissions) {
930
- const f = INTEGRATION_FAMILIES[family];
931
- if (family === "none") return { mode: "none" };
932
- if (family === "hmac") {
933
- return { mode: "hmac", credential: f.credentialFields[0], signatureHeader: `${spec.id}-signature` };
934
- }
935
- if (family === "api-key") {
936
- return { mode: "api_key", credential: apiKeyFieldFor(spec.id), placement: apiKeyPlacementFor(spec.id) };
937
- }
938
- const scopes = permissions.flatMap(
939
- (permission) => permission.providerScopes.map((providerScope) => ({
940
- normalized: permission.normalized,
941
- providerScope,
942
- title: permission.title,
943
- reason: permission.reason,
944
- risk: permission.risk,
945
- dataClass: permission.dataClass
946
- }))
947
- );
948
- return {
949
- mode: "oauth2",
950
- authorizationUrl: f.authorizationUrl ?? `https://example.invalid/${spec.id}/authorize`,
951
- tokenUrl: f.tokenUrl ?? `https://example.invalid/${spec.id}/token`,
952
- clientIdEnv: f.credentialFields.find((field) => !field.secret)?.env,
953
- clientSecretEnv: f.credentialFields.find((field) => field.secret)?.env,
954
- scopes,
955
- extraAuthParams: extraAuthParamsFor(family),
956
- redirectUriTemplate: (f.redirectUriTemplate ?? "https://{host}/api/integrations/oauth/{kind}/callback").replace("{kind}", spec.id),
957
- pkce: family === "google" || family === "microsoft-graph" ? "supported" : "unsupported"
958
- };
959
- }
960
- function credentialFieldsFor(auth) {
961
- if (auth.mode === "api_key" || auth.mode === "hmac") return [auth.credential];
962
- if (auth.mode === "oauth2") {
963
- return [
964
- { label: "Client ID", env: auth.clientIdEnv, description: "OAuth client ID.", secret: false },
965
- { label: "Client Secret", env: auth.clientSecretEnv, description: "OAuth client secret.", secret: true }
966
- ];
967
- }
968
- return [];
969
- }
970
- function permissionsFor(spec, actions) {
971
- const dataClass = dataClassFor3(actions);
972
- const readScope = providerScopeFor(spec, "read");
973
- const writeScope = providerScopeFor(spec, "write");
974
- const permissions = [
975
- {
976
- normalized: `${spec.actionPack}.read`,
977
- providerScopes: readScope ? [readScope] : [],
978
- title: `${spec.title} read`,
979
- risk: "read",
980
- dataClass,
981
- reason: `Read ${spec.title} data for user-authorized agent workflows.`
982
- }
983
- ];
984
- if (actions.some((a) => a.risk !== "read")) {
985
- permissions.push({
986
- normalized: `${spec.actionPack}.write`,
987
- providerScopes: writeScope ? [writeScope] : [],
988
- title: `${spec.title} write`,
989
- risk: "write",
990
- dataClass,
991
- reason: `Create or update ${spec.title} resources after policy approval.`
992
- });
993
- }
994
- return permissions;
995
- }
996
- function providerScopeFor(spec, mode) {
997
- const explicit = explicitScopes[spec.id]?.[mode];
998
- if (explicit) return explicit;
999
- if (spec.auth === "none") return "";
1000
- return `${spec.id}.${mode}`;
1001
- }
1002
- var explicitScopes = {
1003
- gmail: { read: "https://www.googleapis.com/auth/gmail.readonly", write: "https://www.googleapis.com/auth/gmail.modify" },
1004
- "google-calendar": { read: "https://www.googleapis.com/auth/calendar.readonly", write: "https://www.googleapis.com/auth/calendar" },
1005
- "google-sheets": { read: "https://www.googleapis.com/auth/spreadsheets.readonly", write: "https://www.googleapis.com/auth/spreadsheets" },
1006
- "google-drive": { read: "https://www.googleapis.com/auth/drive.readonly", write: "https://www.googleapis.com/auth/drive.file" },
1007
- "google-docs": { read: "https://www.googleapis.com/auth/documents.readonly", write: "https://www.googleapis.com/auth/documents" },
1008
- "outlook-mail": { read: "Mail.Read", write: "Mail.Send" },
1009
- "outlook-calendar": { read: "Calendars.Read", write: "Calendars.ReadWrite" },
1010
- "microsoft-teams": { read: "ChannelMessage.Read.All", write: "ChannelMessage.Send" },
1011
- onedrive: { read: "Files.Read", write: "Files.ReadWrite" },
1012
- sharepoint: { read: "Sites.Read.All", write: "Sites.ReadWrite.All" },
1013
- slack: { read: "channels:read", write: "chat:write" },
1014
- hubspot: { read: "crm.objects.contacts.read", write: "crm.objects.contacts.write" },
1015
- salesforce: { read: "api", write: "api" },
1016
- notion: { read: "", write: "" },
1017
- github: { read: "repo:read", write: "repo" },
1018
- gitlab: { read: "read_api", write: "api" },
1019
- airtable: { read: "data.records:read", write: "data.records:write" },
1020
- asana: { read: "default", write: "default" },
1021
- stripe: { read: "read_only", write: "standard" },
1022
- twilio: { read: "api_key", write: "api_key" }
1023
- };
1024
- function plannerHintsFor(spec, actions) {
1025
- return {
1026
- useFor: spec.domains.map((domain) => domain.replace(/-/g, " ")),
1027
- dataFreshness: ["calendar", "chat", "commerce", "finance", "support"].includes(spec.actionPack) ? "near_realtime" : "eventual",
1028
- writeRisk: actions.some((a) => a.risk === "destructive") ? "high" : actions.some((a) => a.risk === "write") ? "medium" : "low"
1029
- };
1030
- }
1031
- function healthcheckFor(kind, status, auth) {
1032
- if (status !== "executable") {
1033
- return { id: `${kind}.static`, level: "static", description: "Catalog-only integration; no executable connector healthcheck is available yet." };
1034
- }
1035
- if (auth.mode === "oauth2") {
1036
- return { id: `${kind}.connection`, level: "connection", description: "Validate a user connection by calling the connector test endpoint." };
1037
- }
1038
- if (auth.mode === "api_key") {
1039
- return { id: `${kind}.connection`, level: "connection", description: "Validate API credentials by calling the connector test endpoint." };
1040
- }
1041
- if (auth.mode === "hmac") {
1042
- return { id: `${kind}.webhook`, level: "webhook", description: "Validate webhook signing configuration with a signed test payload." };
1043
- }
1044
- return { id: `${kind}.static`, level: "static", description: "No credentials are required." };
1045
- }
1046
- function statusFor(kind) {
1047
- return EXECUTABLE_KINDS.has(kind) ? "executable" : "catalog";
1048
- }
1049
- function dataClassFor3(actions) {
1050
- if (actions.some((a) => a.dataClass === "secret")) return "secret";
1051
- if (actions.some((a) => a.dataClass === "sensitive")) return "sensitive";
1052
- if (actions.some((a) => a.dataClass === "private")) return "private";
1053
- if (actions.some((a) => a.dataClass === "internal")) return "internal";
1054
- return "public";
1055
- }
1056
- function apiKeyFieldFor(kind) {
1057
- return {
1058
- label: `${kind} API key`,
1059
- description: `API key or token for ${kind}.`,
1060
- example: kind === "stripe" ? "sk_live_..." : void 0,
1061
- secret: true
1062
- };
1063
- }
1064
- function apiKeyPlacementFor(kind) {
1065
- if (kind === "gitlab") return "header";
1066
- return "bearer";
1067
- }
1068
- function extraAuthParamsFor(family) {
1069
- if (family === "google") return { access_type: "offline", prompt: "consent", include_granted_scopes: "true" };
1070
- if (family === "notion") return { owner: "user" };
1071
- return void 0;
1072
- }
1073
-
1074
235
  // src/registry.ts
1075
236
  var DEFAULT_ALIASES = {
1076
237
  notion: "notion-database",
@@ -1247,8 +408,8 @@ function mergeActions(candidates) {
1247
408
  function mergeTriggers(candidates) {
1248
409
  const out = /* @__PURE__ */ new Map();
1249
410
  for (const candidate of toolBindableCandidates(candidates)) {
1250
- for (const trigger of candidate.connector.triggers ?? []) {
1251
- if (!out.has(trigger.id)) out.set(trigger.id, trigger);
411
+ for (const trigger2 of candidate.connector.triggers ?? []) {
412
+ if (!out.has(trigger2.id)) out.set(trigger2.id, trigger2);
1252
413
  }
1253
414
  }
1254
415
  return out.size > 0 ? [...out.values()] : void 0;
@@ -1521,6 +682,228 @@ function approvalInputHash(input) {
1521
682
  return createHash("sha256").update(JSON.stringify(input ?? null)).digest("base64url");
1522
683
  }
1523
684
 
685
+ // src/actions.ts
686
+ var CANONICAL_INTEGRATION_ACTIONS = {
687
+ googleCalendarEventsList: "google-calendar.events.list",
688
+ googleCalendarEventsCreate: "google-calendar.events.create",
689
+ gmailMessagesSearch: "gmail.messages.search",
690
+ gmailMessagesSend: "gmail.messages.send",
691
+ googleDriveFilesSearch: "google-drive.files.search",
692
+ googleDriveFilesRead: "google-drive.files.read",
693
+ githubRepositoriesGet: "github.repositories.get",
694
+ githubIssuesSearch: "github.issues.search",
695
+ githubIssuesCreate: "github.issues.create",
696
+ githubPullRequestsComment: "github.pull-requests.comment",
697
+ slackChannelsList: "slack.channels.list",
698
+ slackMessagesSearch: "slack.messages.search",
699
+ slackMessagesPost: "slack.messages.post",
700
+ providerHttpRequest: "provider.http.request"
701
+ };
702
+ function buildCanonicalLaunchConnectors(options = {}) {
703
+ const providerId = options.providerId ?? "tangle-platform";
704
+ const connectors = [
705
+ googleCalendarConnector(providerId),
706
+ gmailConnector(providerId),
707
+ googleDriveConnector(providerId),
708
+ githubConnector(providerId),
709
+ slackConnector(providerId)
710
+ ];
711
+ if (!options.includeProviderPassthrough) return connectors;
712
+ return connectors.map((connector) => ({
713
+ ...connector,
714
+ actions: [...connector.actions, providerPassthroughAction(connector.id)]
715
+ }));
716
+ }
717
+ function canonicalActionConnectorId(actionId) {
718
+ if (actionId.startsWith("google-calendar.")) return "google-calendar";
719
+ if (actionId.startsWith("gmail.")) return "gmail";
720
+ if (actionId.startsWith("google-drive.")) return "google-drive";
721
+ if (actionId.startsWith("github.")) return "github";
722
+ if (actionId.startsWith("slack.")) return "slack";
723
+ if (actionId === CANONICAL_INTEGRATION_ACTIONS.providerHttpRequest) return void 0;
724
+ return actionId.split(".")[0];
725
+ }
726
+ function googleCalendarConnector(providerId) {
727
+ return {
728
+ id: "google-calendar",
729
+ providerId,
730
+ title: "Google Calendar",
731
+ category: "calendar",
732
+ auth: "oauth2",
733
+ scopes: ["https://www.googleapis.com/auth/calendar.readonly", "https://www.googleapis.com/auth/calendar.events"],
734
+ actions: [
735
+ {
736
+ id: CANONICAL_INTEGRATION_ACTIONS.googleCalendarEventsList,
737
+ title: "List calendar events",
738
+ risk: "read",
739
+ requiredScopes: ["https://www.googleapis.com/auth/calendar.readonly"],
740
+ dataClass: "private",
741
+ description: "Read events from a Google Calendar over a bounded time range.",
742
+ inputSchema: objectSchema({
743
+ calendarId: { type: "string", default: "primary" },
744
+ timeMin: { type: "string", description: "RFC3339 lower bound." },
745
+ timeMax: { type: "string", description: "RFC3339 upper bound." }
746
+ }, ["timeMin", "timeMax"])
747
+ },
748
+ {
749
+ id: CANONICAL_INTEGRATION_ACTIONS.googleCalendarEventsCreate,
750
+ title: "Create calendar event",
751
+ risk: "write",
752
+ requiredScopes: ["https://www.googleapis.com/auth/calendar.events"],
753
+ dataClass: "private",
754
+ approvalRequired: true,
755
+ description: "Create an event on a Google Calendar after user approval.",
756
+ inputSchema: objectSchema({
757
+ calendarId: { type: "string", default: "primary" },
758
+ start: { type: "string", description: "RFC3339 start time." },
759
+ end: { type: "string", description: "RFC3339 end time." },
760
+ summary: { type: "string" },
761
+ description: { type: "string" },
762
+ attendees: { type: "array", items: { type: "string" } }
763
+ }, ["start", "end", "summary"])
764
+ }
765
+ ],
766
+ metadata: { source: "canonical-launch", supportTier: "setupReady" }
767
+ };
768
+ }
769
+ function gmailConnector(providerId) {
770
+ return {
771
+ id: "gmail",
772
+ providerId,
773
+ title: "Gmail",
774
+ category: "email",
775
+ auth: "oauth2",
776
+ scopes: ["https://www.googleapis.com/auth/gmail.readonly", "https://www.googleapis.com/auth/gmail.send"],
777
+ actions: [
778
+ {
779
+ id: CANONICAL_INTEGRATION_ACTIONS.gmailMessagesSearch,
780
+ title: "Search Gmail messages",
781
+ risk: "read",
782
+ requiredScopes: ["https://www.googleapis.com/auth/gmail.readonly"],
783
+ dataClass: "private",
784
+ description: "Search user Gmail messages and return bounded message metadata/snippets.",
785
+ inputSchema: objectSchema({ query: { type: "string" }, maxResults: { type: "integer", minimum: 1, maximum: 50 } }, ["query"])
786
+ },
787
+ {
788
+ id: CANONICAL_INTEGRATION_ACTIONS.gmailMessagesSend,
789
+ title: "Send Gmail message",
790
+ risk: "write",
791
+ requiredScopes: ["https://www.googleapis.com/auth/gmail.send"],
792
+ dataClass: "private",
793
+ approvalRequired: true,
794
+ description: "Send an email from the user account after approval.",
795
+ inputSchema: objectSchema({
796
+ to: { type: "array", items: { type: "string" } },
797
+ subject: { type: "string" },
798
+ body: { type: "string" }
799
+ }, ["to", "subject", "body"])
800
+ }
801
+ ],
802
+ triggers: [{
803
+ id: "gmail.messages.received",
804
+ title: "Gmail message received",
805
+ requiredScopes: ["https://www.googleapis.com/auth/gmail.readonly"],
806
+ dataClass: "private",
807
+ description: "Triggered when a new matching Gmail message is received."
808
+ }],
809
+ metadata: { source: "canonical-launch", supportTier: "setupReady" }
810
+ };
811
+ }
812
+ function googleDriveConnector(providerId) {
813
+ return {
814
+ id: "google-drive",
815
+ providerId,
816
+ title: "Google Drive",
817
+ category: "storage",
818
+ auth: "oauth2",
819
+ scopes: ["https://www.googleapis.com/auth/drive.readonly", "https://www.googleapis.com/auth/drive.file"],
820
+ actions: [
821
+ {
822
+ id: CANONICAL_INTEGRATION_ACTIONS.googleDriveFilesSearch,
823
+ title: "Search Drive files",
824
+ risk: "read",
825
+ requiredScopes: ["https://www.googleapis.com/auth/drive.readonly"],
826
+ dataClass: "private",
827
+ description: "Search user-visible Google Drive files.",
828
+ inputSchema: objectSchema({ query: { type: "string" }, maxResults: { type: "integer", minimum: 1, maximum: 50 } }, ["query"])
829
+ },
830
+ {
831
+ id: CANONICAL_INTEGRATION_ACTIONS.googleDriveFilesRead,
832
+ title: "Read Drive file",
833
+ risk: "read",
834
+ requiredScopes: ["https://www.googleapis.com/auth/drive.readonly"],
835
+ dataClass: "private",
836
+ description: "Read metadata and content for an authorized Drive file.",
837
+ inputSchema: objectSchema({ fileId: { type: "string" } }, ["fileId"])
838
+ }
839
+ ],
840
+ metadata: { source: "canonical-launch", supportTier: "setupReady" }
841
+ };
842
+ }
843
+ function githubConnector(providerId) {
844
+ return {
845
+ id: "github",
846
+ providerId,
847
+ title: "GitHub",
848
+ category: "workflow",
849
+ auth: "oauth2",
850
+ scopes: ["repo", "read:user"],
851
+ actions: [
852
+ readAction(CANONICAL_INTEGRATION_ACTIONS.githubRepositoriesGet, "Read repository metadata", ["repo"], objectSchema({ owner: { type: "string" }, repo: { type: "string" } }, ["owner", "repo"])),
853
+ readAction(CANONICAL_INTEGRATION_ACTIONS.githubIssuesSearch, "Search issues and pull requests", ["repo"], objectSchema({ query: { type: "string" }, limit: { type: "integer", minimum: 1, maximum: 50 } }, ["query"])),
854
+ writeAction(CANONICAL_INTEGRATION_ACTIONS.githubIssuesCreate, "Create issue", ["repo"], objectSchema({ owner: { type: "string" }, repo: { type: "string" }, title: { type: "string" }, body: { type: "string" } }, ["owner", "repo", "title"])),
855
+ writeAction(CANONICAL_INTEGRATION_ACTIONS.githubPullRequestsComment, "Comment on pull request", ["repo"], objectSchema({ owner: { type: "string" }, repo: { type: "string" }, pullNumber: { type: "integer" }, body: { type: "string" } }, ["owner", "repo", "pullNumber", "body"]))
856
+ ],
857
+ metadata: { source: "canonical-launch", supportTier: "setupReady" }
858
+ };
859
+ }
860
+ function slackConnector(providerId) {
861
+ return {
862
+ id: "slack",
863
+ providerId,
864
+ title: "Slack",
865
+ category: "chat",
866
+ auth: "oauth2",
867
+ scopes: ["channels:read", "search:read", "chat:write"],
868
+ actions: [
869
+ readAction(CANONICAL_INTEGRATION_ACTIONS.slackChannelsList, "List Slack channels", ["channels:read"], objectSchema({ limit: { type: "integer", minimum: 1, maximum: 200 } })),
870
+ readAction(CANONICAL_INTEGRATION_ACTIONS.slackMessagesSearch, "Search Slack messages", ["search:read"], objectSchema({ query: { type: "string" }, count: { type: "integer", minimum: 1, maximum: 50 } }, ["query"])),
871
+ writeAction(CANONICAL_INTEGRATION_ACTIONS.slackMessagesPost, "Post Slack message", ["chat:write"], objectSchema({ channel: { type: "string" }, text: { type: "string" }, blocks: { type: "array" } }, ["channel", "text"]))
872
+ ],
873
+ triggers: [trigger("slack.message.posted", "Slack message posted", ["channels:read"])],
874
+ metadata: { source: "canonical-launch", supportTier: "setupReady" }
875
+ };
876
+ }
877
+ function readAction(id, title, scopes, inputSchema) {
878
+ return { id, title, risk: "read", requiredScopes: scopes, dataClass: "private", inputSchema };
879
+ }
880
+ function writeAction(id, title, scopes, inputSchema) {
881
+ return { id, title, risk: "write", requiredScopes: scopes, dataClass: "private", approvalRequired: true, inputSchema };
882
+ }
883
+ function trigger(id, title, scopes) {
884
+ return { id, title, requiredScopes: scopes, dataClass: "private" };
885
+ }
886
+ function providerPassthroughAction(connectorId) {
887
+ return {
888
+ id: CANONICAL_INTEGRATION_ACTIONS.providerHttpRequest,
889
+ title: "Provider HTTP request",
890
+ risk: "write",
891
+ requiredScopes: [],
892
+ dataClass: "sensitive",
893
+ approvalRequired: true,
894
+ description: `Controlled provider-native passthrough for ${connectorId}. Disabled by default by platform policy.`,
895
+ inputSchema: objectSchema({
896
+ method: { type: "string", enum: ["GET", "POST", "PUT", "PATCH", "DELETE"] },
897
+ path: { type: "string" },
898
+ query: { type: "object" },
899
+ body: { type: "object" }
900
+ }, ["method", "path"])
901
+ };
902
+ }
903
+ function objectSchema(properties, required = []) {
904
+ return { type: "object", additionalProperties: false, properties, required };
905
+ }
906
+
1524
907
  // src/bridge.ts
1525
908
  var DEFAULT_INTEGRATION_BRIDGE_ENV = "TANGLE_INTEGRATION_BUNDLE";
1526
909
  function buildIntegrationBridgePayload(bundle) {
@@ -1586,6 +969,219 @@ function assertBridgePayload(value) {
1586
969
  if (!Array.isArray(payload.tools)) throw new Error("Invalid integration bridge tools.");
1587
970
  }
1588
971
 
972
+ // src/errors.ts
973
+ var IntegrationRuntimeError = class extends Error {
974
+ code;
975
+ status;
976
+ userAction;
977
+ metadata;
978
+ constructor(input) {
979
+ super(input.message);
980
+ this.name = "IntegrationRuntimeError";
981
+ this.code = input.code;
982
+ this.status = input.status ?? statusForCode(input.code);
983
+ this.userAction = input.userAction;
984
+ this.metadata = input.metadata;
985
+ }
986
+ };
987
+ function normalizeIntegrationError(error) {
988
+ if (error instanceof IntegrationRuntimeError) {
989
+ return {
990
+ ok: false,
991
+ code: error.code,
992
+ message: error.message,
993
+ status: error.status,
994
+ userAction: error.userAction,
995
+ metadata: redactUnknown2(error.metadata)
996
+ };
997
+ }
998
+ const message = error instanceof Error ? error.message : String(error ?? "Unknown integration error.");
999
+ return {
1000
+ ok: false,
1001
+ code: inferCode(message),
1002
+ message,
1003
+ status: 500
1004
+ };
1005
+ }
1006
+ function statusForCode(code) {
1007
+ if (code === "missing_connection" || code === "missing_grant") return 409;
1008
+ if (code === "approval_required") return 202;
1009
+ if (code === "approval_denied") return 403;
1010
+ if (code === "connection_revoked" || code === "connection_expired" || code === "provider_auth_failed") return 401;
1011
+ if (code === "scope_missing" || code === "action_denied" || code === "passthrough_disabled") return 403;
1012
+ if (code === "action_not_found" || code === "manifest_invalid" || code === "input_invalid") return 400;
1013
+ if (code === "provider_rate_limited") return 429;
1014
+ if (code === "provider_unavailable") return 503;
1015
+ if (code === "capability_expired" || code === "capability_invalid") return 401;
1016
+ return 500;
1017
+ }
1018
+ function inferCode(message) {
1019
+ if (/approval/i.test(message)) return "approval_required";
1020
+ if (/scope/i.test(message)) return "scope_missing";
1021
+ if (/expired/i.test(message)) return "connection_expired";
1022
+ if (/revoked/i.test(message)) return "connection_revoked";
1023
+ if (/rate.?limit|429/i.test(message)) return "provider_rate_limited";
1024
+ if (/unauth|forbidden|401|403/i.test(message)) return "provider_auth_failed";
1025
+ return "unknown";
1026
+ }
1027
+ function redactUnknown2(value) {
1028
+ if (Array.isArray(value)) return value.map(redactUnknown2);
1029
+ if (!value || typeof value !== "object") return value;
1030
+ const out = {};
1031
+ for (const [key, child] of Object.entries(value)) {
1032
+ if (/token|secret|password|authorization|api[_-]?key|credential|refresh/i.test(key)) {
1033
+ out[key] = "[REDACTED]";
1034
+ } else {
1035
+ out[key] = redactUnknown2(child);
1036
+ }
1037
+ }
1038
+ return out;
1039
+ }
1040
+
1041
+ // src/client.ts
1042
+ var TangleIntegrationsClient = class {
1043
+ endpoint;
1044
+ bridge;
1045
+ fetchImpl;
1046
+ getCapabilityToken;
1047
+ constructor(options) {
1048
+ this.endpoint = options.endpoint.replace(/\/$/, "");
1049
+ this.bridge = options.bridge ?? parseIntegrationBridgeEnvironment(
1050
+ options.env ?? readProcessEnv(),
1051
+ { envVar: options.envVar ?? DEFAULT_INTEGRATION_BRIDGE_ENV }
1052
+ );
1053
+ this.fetchImpl = options.fetchImpl ?? fetch;
1054
+ this.getCapabilityToken = options.getCapabilityToken ?? ((tool) => tool.capabilityToken);
1055
+ }
1056
+ tools() {
1057
+ return [...this.bridge.tools];
1058
+ }
1059
+ findTool(toolOrAction) {
1060
+ const found = this.bridge.tools.find(
1061
+ (tool) => tool.name === toolOrAction || tool.action === toolOrAction || `${tool.connectorId}.${tool.action}` === toolOrAction
1062
+ );
1063
+ if (!found) {
1064
+ throw new IntegrationRuntimeError({
1065
+ code: "action_not_found",
1066
+ message: `Integration tool ${toolOrAction} is not available in this runtime.`,
1067
+ metadata: { available: this.bridge.tools.map((tool) => ({ name: tool.name, action: tool.action, connectorId: tool.connectorId })) }
1068
+ });
1069
+ }
1070
+ return found;
1071
+ }
1072
+ async invoke(input) {
1073
+ try {
1074
+ const tool = this.findTool(input.tool);
1075
+ const token = await this.getCapabilityToken(tool);
1076
+ const response = await this.fetchImpl(`${this.endpoint}/v1/integrations/invoke`, {
1077
+ method: "POST",
1078
+ headers: {
1079
+ "content-type": "application/json",
1080
+ authorization: `Bearer ${token}`
1081
+ },
1082
+ body: JSON.stringify({
1083
+ action: tool.action,
1084
+ input: input.input,
1085
+ idempotencyKey: input.idempotencyKey ?? defaultIdempotencyKey(tool.action),
1086
+ dryRun: input.dryRun,
1087
+ metadata: input.metadata
1088
+ })
1089
+ });
1090
+ const json = await response.json().catch(() => void 0);
1091
+ if (!response.ok && !json) {
1092
+ return { status: "failed", action: tool.action, error: `Integration invoke failed with HTTP ${response.status}` };
1093
+ }
1094
+ return json ?? { status: "failed", action: tool.action, error: "Integration invoke returned an empty response." };
1095
+ } catch (error) {
1096
+ const normalized = normalizeIntegrationError(error);
1097
+ return { status: "failed", action: input.tool, error: normalized.message, metadata: { code: normalized.code, userAction: normalized.userAction } };
1098
+ }
1099
+ }
1100
+ };
1101
+ function createTangleIntegrationsClient(options) {
1102
+ return new TangleIntegrationsClient(options);
1103
+ }
1104
+ function defaultIdempotencyKey(action) {
1105
+ return `${action}:${Date.now()}:${Math.random().toString(36).slice(2)}`;
1106
+ }
1107
+ function readProcessEnv() {
1108
+ if (typeof process !== "undefined" && process.env) return process.env;
1109
+ return {};
1110
+ }
1111
+
1112
+ // src/consent.ts
1113
+ function renderConsentSummary(manifestOrResolution, options = {}) {
1114
+ const manifest = "manifest" in manifestOrResolution ? manifestOrResolution.manifest : manifestOrResolution;
1115
+ const appName = options.appName ?? manifest.title ?? manifest.id;
1116
+ const requirements = manifest.requirements;
1117
+ const risk = aggregateRisk(requirements, options.connectors);
1118
+ const connectorIds = unique2(requirements.map((requirement) => requirement.connectorId));
1119
+ const first = requirements[0];
1120
+ const body = first ? sentenceForRequirement(appName, first) : `${appName} does not request integrations.`;
1121
+ return {
1122
+ title: `${appName} wants to use ${humanList(connectorIds.map(titleize))}`,
1123
+ body,
1124
+ bullets: requirements.map((requirement) => bulletForRequirement(requirement, options.connectors)),
1125
+ primaryAction: risk === "read" ? "Allow access" : risk === "write" ? "Review and allow" : "Review destructive access",
1126
+ risk,
1127
+ connectorIds
1128
+ };
1129
+ }
1130
+ function renderApprovalCopy(input) {
1131
+ return {
1132
+ title: `${input.appName} wants to ${input.action.title.toLowerCase()}`,
1133
+ body: `${input.appName} is requesting permission to run "${input.action.title}" on ${input.connectorTitle}.`,
1134
+ bullets: [
1135
+ `Risk: ${input.action.risk}`,
1136
+ `Data: ${input.action.dataClass}`,
1137
+ ...input.approvalId ? [`Approval id: ${input.approvalId}`] : []
1138
+ ],
1139
+ primaryAction: input.action.risk === "read" ? "Allow" : "Approve action",
1140
+ risk: input.action.risk,
1141
+ connectorIds: []
1142
+ };
1143
+ }
1144
+ function sentenceForRequirement(appName, requirement) {
1145
+ if (requirement.connectorId === "google-calendar" && requirement.mode === "read") {
1146
+ return `${appName} wants to read your Google Calendar to find schedule-aware recommendations.`;
1147
+ }
1148
+ if (requirement.connectorId === "google-calendar" && requirement.mode === "write") {
1149
+ return `${appName} wants to create or update Google Calendar events after your approval.`;
1150
+ }
1151
+ if (requirement.mode === "read") return `${appName} wants to read ${titleize(requirement.connectorId)} data.`;
1152
+ if (requirement.mode === "write") return `${appName} wants to write ${titleize(requirement.connectorId)} data after approval.`;
1153
+ return `${appName} wants to subscribe to ${titleize(requirement.connectorId)} events.`;
1154
+ }
1155
+ function bulletForRequirement(requirement, connectors = []) {
1156
+ const connector = connectors.find((candidate) => candidate.id === requirement.connectorId);
1157
+ const actions = requirement.requiredActions?.length ? requirement.requiredActions.map((id) => connector?.actions.find((action) => action.id === id)?.title ?? id) : requirement.requiredTriggers ?? [];
1158
+ return `${titleize(requirement.connectorId)}: ${requirement.reason}${actions.length ? ` (${actions.join(", ")})` : ""}`;
1159
+ }
1160
+ function aggregateRisk(requirements, connectors = []) {
1161
+ let rank = 0;
1162
+ for (const requirement of requirements) {
1163
+ if (requirement.mode === "write") rank = Math.max(rank, 1);
1164
+ const connector = connectors.find((candidate) => candidate.id === requirement.connectorId);
1165
+ for (const actionId of requirement.requiredActions ?? []) {
1166
+ const risk = connector?.actions.find((action) => action.id === actionId)?.risk;
1167
+ if (risk === "write") rank = Math.max(rank, 1);
1168
+ if (risk === "destructive") rank = Math.max(rank, 2);
1169
+ }
1170
+ }
1171
+ return rank === 2 ? "destructive" : rank === 1 ? "write" : "read";
1172
+ }
1173
+ function humanList(values) {
1174
+ if (values.length <= 1) return values[0] ?? "integrations";
1175
+ if (values.length === 2) return `${values[0]} and ${values[1]}`;
1176
+ return `${values.slice(0, -1).join(", ")}, and ${values.at(-1)}`;
1177
+ }
1178
+ function titleize(value) {
1179
+ return value.split(/[-_.]/g).filter(Boolean).map((part) => part[0].toUpperCase() + part.slice(1)).join(" ");
1180
+ }
1181
+ function unique2(values) {
1182
+ return [...new Set(values)];
1183
+ }
1184
+
1589
1185
  // src/adapter-provider.ts
1590
1186
  function createConnectorAdapterProvider(options) {
1591
1187
  const providerId = options.id ?? "first-party";
@@ -2077,80 +1673,330 @@ async function runIntegrationHealthcheck(input) {
2077
1673
  }));
2078
1674
  return result;
2079
1675
  }
2080
- async function runIntegrationHealthchecks(input) {
2081
- const results = [];
2082
- for (const connection of input.connections) {
2083
- const result = await runIntegrationHealthcheck({
2084
- connection,
2085
- registry: input.registry,
2086
- test: input.test,
2087
- audit: input.audit,
2088
- now: input.now
2089
- });
2090
- await input.store?.put(result);
2091
- results.push(result);
1676
+ async function runIntegrationHealthchecks(input) {
1677
+ const results = [];
1678
+ for (const connection of input.connections) {
1679
+ const result = await runIntegrationHealthcheck({
1680
+ connection,
1681
+ registry: input.registry,
1682
+ test: input.test,
1683
+ audit: input.audit,
1684
+ now: input.now
1685
+ });
1686
+ await input.store?.put(result);
1687
+ results.push(result);
1688
+ }
1689
+ return results;
1690
+ }
1691
+ function healthcheckRequest(action = "healthcheck") {
1692
+ return {
1693
+ connectionId: "__healthcheck__",
1694
+ action,
1695
+ input: {},
1696
+ dryRun: true,
1697
+ metadata: { healthcheck: true }
1698
+ };
1699
+ }
1700
+ function connectionStatusCheck(connection, now) {
1701
+ if (connection.status !== "active") {
1702
+ return { id: "connection-active", status: "unhealthy", message: `Connection is ${connection.status}.` };
1703
+ }
1704
+ if (connection.expiresAt && Date.parse(connection.expiresAt) <= now().getTime()) {
1705
+ return { id: "connection-active", status: "unhealthy", message: "Connection credentials are expired." };
1706
+ }
1707
+ return { id: "connection-active", status: "healthy", message: "Connection is active." };
1708
+ }
1709
+ function connectorExecutableCheck(connector) {
1710
+ const executable = connector.actions.length > 0 || (connector.triggers?.length ?? 0) > 0;
1711
+ if (!executable) {
1712
+ return { id: "connector-executable", status: "degraded", message: `${connector.title} is catalog-only.` };
1713
+ }
1714
+ return { id: "connector-executable", status: "healthy", message: `${connector.title} has executable actions or triggers.` };
1715
+ }
1716
+ function scopeShapeCheck(connection, connector) {
1717
+ const declaredScopes = new Set(connector.scopes);
1718
+ const undeclared = connection.grantedScopes.filter((scope) => !declaredScopes.has(scope));
1719
+ if (connector.scopes.length === 0 && connection.grantedScopes.length > 0) {
1720
+ return { id: "scope-shape", status: "unknown", message: "Connector does not declare a scope catalog.", metadata: { grantedScopes: connection.grantedScopes } };
1721
+ }
1722
+ if (undeclared.length > 0) {
1723
+ return { id: "scope-shape", status: "degraded", message: "Connection has scopes not declared by the connector.", metadata: { undeclared } };
1724
+ }
1725
+ return { id: "scope-shape", status: "healthy", message: "Granted scopes match the connector shape." };
1726
+ }
1727
+ async function liveHealthcheck(connection, connector, test) {
1728
+ try {
1729
+ const result = await test(connection, connector);
1730
+ const ok = typeof result === "boolean" ? result : result.ok;
1731
+ return {
1732
+ id: "provider-live-test",
1733
+ status: ok ? "healthy" : "unhealthy",
1734
+ message: ok ? "Provider live test passed." : "Provider live test failed.",
1735
+ metadata: typeof result === "boolean" ? void 0 : { action: result.action, warnings: result.warnings }
1736
+ };
1737
+ } catch (error) {
1738
+ return {
1739
+ id: "provider-live-test",
1740
+ status: "unhealthy",
1741
+ message: error instanceof Error ? error.message : "Provider live test failed."
1742
+ };
1743
+ }
1744
+ }
1745
+ function rollupHealthStatus(checks) {
1746
+ if (checks.some((check) => check.status === "unhealthy")) return "unhealthy";
1747
+ if (checks.some((check) => check.status === "degraded")) return "degraded";
1748
+ if (checks.some((check) => check.status === "unknown")) return "unknown";
1749
+ return "healthy";
1750
+ }
1751
+
1752
+ // src/manifest.ts
1753
+ function validateIntegrationManifest(manifest) {
1754
+ const issues = [];
1755
+ if (!manifest.id?.trim()) issues.push({ path: "id", message: "Manifest id is required." });
1756
+ if (!Array.isArray(manifest.requirements)) issues.push({ path: "requirements", message: "Requirements must be an array." });
1757
+ const ids = /* @__PURE__ */ new Set();
1758
+ for (const [index, requirement] of (manifest.requirements ?? []).entries()) {
1759
+ const path = `requirements[${index}]`;
1760
+ if (!requirement.id?.trim()) issues.push({ path: `${path}.id`, message: "Requirement id is required." });
1761
+ if (ids.has(requirement.id)) issues.push({ path: `${path}.id`, message: `Duplicate requirement id ${requirement.id}.` });
1762
+ ids.add(requirement.id);
1763
+ if (!requirement.connectorId?.trim()) issues.push({ path: `${path}.connectorId`, message: "Connector id is required." });
1764
+ if (!["read", "write", "trigger"].includes(requirement.mode)) issues.push({ path: `${path}.mode`, message: "Mode must be read, write, or trigger." });
1765
+ if (!requirement.reason?.trim()) issues.push({ path: `${path}.reason`, message: "Human-readable reason is required." });
1766
+ if (requirement.mode !== "trigger" && !requirement.requiredActions?.length) {
1767
+ issues.push({ path: `${path}.requiredActions`, message: "Non-trigger requirements should list required actions." });
1768
+ }
1769
+ if (requirement.mode === "trigger" && !requirement.requiredTriggers?.length) {
1770
+ issues.push({ path: `${path}.requiredTriggers`, message: "Trigger requirements should list required triggers." });
1771
+ }
1772
+ }
1773
+ return { ok: issues.length === 0, issues };
1774
+ }
1775
+ function assertValidIntegrationManifest(manifest) {
1776
+ const result = validateIntegrationManifest(manifest);
1777
+ if (!result.ok) {
1778
+ throw new Error(`Invalid integration manifest: ${result.issues.map((issue) => `${issue.path}: ${issue.message}`).join("; ")}`);
1779
+ }
1780
+ }
1781
+ function inferIntegrationManifestFromTools(options) {
1782
+ const byConnector = /* @__PURE__ */ new Map();
1783
+ for (const item of options.tools) {
1784
+ const action = typeof item === "string" ? item : item.action;
1785
+ const connectorId = typeof item === "string" ? canonicalActionConnectorId(action) : item.connectorId ?? canonicalActionConnectorId(action);
1786
+ if (!connectorId) continue;
1787
+ const mode = typeof item === "string" ? inferMode(action) : item.mode ?? inferMode(action);
1788
+ const id = `${connectorId}-${mode}`;
1789
+ const existing = byConnector.get(id);
1790
+ const reason = typeof item === "string" ? defaultReason(connectorId, mode) : item.reason ?? defaultReason(connectorId, mode);
1791
+ if (existing) {
1792
+ byConnector.set(id, {
1793
+ ...existing,
1794
+ requiredActions: unique3([...existing.requiredActions ?? [], action]),
1795
+ requiredScopes: unique3([...existing.requiredScopes ?? [], ...typeof item === "string" ? [] : item.scopes ?? []])
1796
+ });
1797
+ } else {
1798
+ byConnector.set(id, {
1799
+ id,
1800
+ connectorId,
1801
+ mode,
1802
+ reason,
1803
+ requiredActions: mode === "trigger" ? void 0 : [action],
1804
+ requiredScopes: typeof item === "string" ? void 0 : item.scopes
1805
+ });
1806
+ }
1807
+ }
1808
+ const manifest = {
1809
+ id: options.manifestId,
1810
+ title: options.title,
1811
+ requirements: [...byConnector.values()],
1812
+ metadata: options.metadata
1813
+ };
1814
+ assertValidIntegrationManifest(manifest);
1815
+ return manifest;
1816
+ }
1817
+ function explainMissingRequirements(resolution) {
1818
+ return [...resolution.missing, ...resolution.optionalMissing].map((item) => ({
1819
+ requirementId: item.requirement.id,
1820
+ connectorId: item.requirement.connectorId,
1821
+ status: item.status,
1822
+ message: item.message,
1823
+ userAction: item.requirement.optional ? "ignore_optional" : item.status === "not_executable" ? "enable" : "connect"
1824
+ }));
1825
+ }
1826
+ function calendarExercisePlannerManifest(id = "exercise-calendar-planner") {
1827
+ return {
1828
+ id,
1829
+ title: "Exercise Calendar Planner",
1830
+ requirements: [{
1831
+ id: "calendar-read",
1832
+ connectorId: "google-calendar",
1833
+ mode: "read",
1834
+ reason: "Read busy and free calendar windows to recommend exercise sessions.",
1835
+ requiredActions: [CANONICAL_INTEGRATION_ACTIONS.googleCalendarEventsList],
1836
+ requiredScopes: ["https://www.googleapis.com/auth/calendar.readonly"]
1837
+ }]
1838
+ };
1839
+ }
1840
+ function inferMode(action) {
1841
+ if (/(create|send|post|update|delete|write|comment|request)$/i.test(action)) return "write";
1842
+ return "read";
1843
+ }
1844
+ function defaultReason(connectorId, mode) {
1845
+ if (connectorId === "google-calendar" && mode === "read") return "Read calendar availability for the generated app.";
1846
+ if (connectorId === "google-calendar" && mode === "write") return "Create or update calendar events after user approval.";
1847
+ return `${mode === "read" ? "Read from" : mode === "write" ? "Write to" : "Subscribe to"} ${connectorId} for this app.`;
1848
+ }
1849
+ function unique3(values) {
1850
+ return [...new Set(values)];
1851
+ }
1852
+
1853
+ // src/passthrough.ts
1854
+ var PROVIDER_PASSTHROUGH_ACTION = CANONICAL_INTEGRATION_ACTIONS.providerHttpRequest;
1855
+ function validateProviderPassthroughRequest(input, policy) {
1856
+ if (!policy.enabled) {
1857
+ throw new IntegrationRuntimeError({
1858
+ code: "passthrough_disabled",
1859
+ message: "Provider-native passthrough is disabled for this connector."
1860
+ });
1861
+ }
1862
+ if (!input.path.startsWith("/")) {
1863
+ throw new IntegrationRuntimeError({ code: "input_invalid", message: "Provider passthrough path must start with /." });
1864
+ }
1865
+ if (policy.allowedMethods?.length && !policy.allowedMethods.includes(input.method)) {
1866
+ throw new IntegrationRuntimeError({ code: "action_denied", message: `Provider passthrough method ${input.method} is not allowed.` });
1867
+ }
1868
+ if (policy.allowedPathPrefixes?.length && !policy.allowedPathPrefixes.some((prefix) => input.path.startsWith(prefix))) {
1869
+ throw new IntegrationRuntimeError({ code: "action_denied", message: `Provider passthrough path ${input.path} is not allowed.` });
1870
+ }
1871
+ const maxBodyBytes = policy.maxBodyBytes ?? 64 * 1024;
1872
+ const bodyBytes = Buffer.byteLength(JSON.stringify(input.body ?? null), "utf8");
1873
+ if (bodyBytes > maxBodyBytes) {
1874
+ throw new IntegrationRuntimeError({ code: "input_invalid", message: `Provider passthrough body exceeds ${maxBodyBytes} bytes.` });
1875
+ }
1876
+ for (const key of Object.keys(input.headers ?? {})) {
1877
+ if (/authorization|cookie|token|secret|api[_-]?key/i.test(key)) {
1878
+ throw new IntegrationRuntimeError({ code: "input_invalid", message: `Provider passthrough header ${key} is not caller-settable.` });
1879
+ }
1880
+ }
1881
+ }
1882
+
1883
+ // src/policy.ts
1884
+ import { randomUUID as randomUUID2 } from "crypto";
1885
+ var StaticIntegrationPolicyEngine = class {
1886
+ rules;
1887
+ defaultReadEffect;
1888
+ defaultWriteEffect;
1889
+ defaultDestructiveEffect;
1890
+ now;
1891
+ constructor(options = {}) {
1892
+ this.rules = options.rules ?? [];
1893
+ this.defaultReadEffect = options.defaultReadEffect ?? "allow";
1894
+ this.defaultWriteEffect = options.defaultWriteEffect ?? "require_approval";
1895
+ this.defaultDestructiveEffect = options.defaultDestructiveEffect ?? "deny";
1896
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
1897
+ }
1898
+ decide(ctx) {
1899
+ const action = ctx.action;
1900
+ if (!action) return { decision: "deny", reason: "Integration action is missing from connector catalog." };
1901
+ const matched = this.rules.find((rule) => ruleMatches(rule, ctx));
1902
+ const effect = matched?.effect ?? this.defaultEffect(action.risk);
1903
+ const reason = matched?.reason ?? defaultReason2(effect, action.risk);
1904
+ if (effect === "allow") return { decision: "allow", reason, metadata: matched ? { ruleId: matched.id } : void 0 };
1905
+ if (effect === "deny") return { decision: "deny", reason, metadata: matched ? { ruleId: matched.id } : void 0 };
1906
+ return {
1907
+ decision: "require_approval",
1908
+ reason,
1909
+ approval: buildApprovalRequest(ctx, reason, this.now()),
1910
+ metadata: matched ? { ruleId: matched.id } : void 0
1911
+ };
1912
+ }
1913
+ defaultEffect(risk) {
1914
+ if (risk === "read") return this.defaultReadEffect;
1915
+ if (risk === "write") return this.defaultWriteEffect;
1916
+ return this.defaultDestructiveEffect;
1917
+ }
1918
+ };
1919
+ function createDefaultIntegrationPolicyEngine(options = {}) {
1920
+ return new StaticIntegrationPolicyEngine(options);
1921
+ }
1922
+ function buildApprovalRequest(ctx, reason, requestedAt) {
1923
+ if (!ctx.action) {
1924
+ throw new Error("Cannot build approval request without an action descriptor.");
2092
1925
  }
2093
- return results;
1926
+ return {
1927
+ id: `approval_${randomUUID2()}`,
1928
+ connectionId: ctx.connection.id,
1929
+ providerId: ctx.connection.providerId,
1930
+ connectorId: ctx.connection.connectorId,
1931
+ action: ctx.request.action,
1932
+ actor: { type: ctx.subject.type, id: ctx.subject.id },
1933
+ risk: ctx.action.risk,
1934
+ dataClass: ctx.action.dataClass,
1935
+ reason,
1936
+ requestedAt: requestedAt.toISOString(),
1937
+ inputPreview: previewInput(ctx.request.input)
1938
+ };
2094
1939
  }
2095
- function healthcheckRequest(action = "healthcheck") {
1940
+ function redactApprovalRequest(request) {
2096
1941
  return {
2097
- connectionId: "__healthcheck__",
2098
- action,
2099
- input: {},
2100
- dryRun: true,
2101
- metadata: { healthcheck: true }
1942
+ ...request,
1943
+ inputPreview: redactUnknown3(request.inputPreview)
2102
1944
  };
2103
1945
  }
2104
- function connectionStatusCheck(connection, now) {
2105
- if (connection.status !== "active") {
2106
- return { id: "connection-active", status: "unhealthy", message: `Connection is ${connection.status}.` };
2107
- }
2108
- if (connection.expiresAt && Date.parse(connection.expiresAt) <= now().getTime()) {
2109
- return { id: "connection-active", status: "unhealthy", message: "Connection credentials are expired." };
2110
- }
2111
- return { id: "connection-active", status: "healthy", message: "Connection is active." };
1946
+ function ruleMatches(rule, ctx) {
1947
+ if (!ctx.action) return false;
1948
+ if (rule.providerId && rule.providerId !== ctx.connection.providerId) return false;
1949
+ if (rule.connectorId && rule.connectorId !== ctx.connection.connectorId) return false;
1950
+ if (rule.action && rule.action !== ctx.request.action) return false;
1951
+ if (rule.risk && rule.risk !== ctx.action.risk) return false;
1952
+ if (rule.maxRisk && riskRank(ctx.action.risk) > riskRank(rule.maxRisk)) return false;
1953
+ if (rule.dataClass && rule.dataClass !== ctx.action.dataClass) return false;
1954
+ return true;
2112
1955
  }
2113
- function connectorExecutableCheck(connector) {
2114
- const executable = connector.actions.length > 0 || (connector.triggers?.length ?? 0) > 0;
2115
- if (!executable) {
2116
- return { id: "connector-executable", status: "degraded", message: `${connector.title} is catalog-only.` };
2117
- }
2118
- return { id: "connector-executable", status: "healthy", message: `${connector.title} has executable actions or triggers.` };
1956
+ function riskRank(risk) {
1957
+ if (risk === "read") return 0;
1958
+ if (risk === "write") return 1;
1959
+ return 2;
2119
1960
  }
2120
- function scopeShapeCheck(connection, connector) {
2121
- const declaredScopes = new Set(connector.scopes);
2122
- const undeclared = connection.grantedScopes.filter((scope) => !declaredScopes.has(scope));
2123
- if (connector.scopes.length === 0 && connection.grantedScopes.length > 0) {
2124
- return { id: "scope-shape", status: "unknown", message: "Connector does not declare a scope catalog.", metadata: { grantedScopes: connection.grantedScopes } };
2125
- }
2126
- if (undeclared.length > 0) {
2127
- return { id: "scope-shape", status: "degraded", message: "Connection has scopes not declared by the connector.", metadata: { undeclared } };
2128
- }
2129
- return { id: "scope-shape", status: "healthy", message: "Granted scopes match the connector shape." };
1961
+ function defaultReason2(effect, risk) {
1962
+ if (effect === "allow") return `${risk} integration action allowed by default policy.`;
1963
+ if (effect === "deny") return `${risk} integration action denied by default policy.`;
1964
+ return `${risk} integration action requires approval by default policy.`;
2130
1965
  }
2131
- async function liveHealthcheck(connection, connector, test) {
2132
- try {
2133
- const result = await test(connection, connector);
2134
- const ok = typeof result === "boolean" ? result : result.ok;
2135
- return {
2136
- id: "provider-live-test",
2137
- status: ok ? "healthy" : "unhealthy",
2138
- message: ok ? "Provider live test passed." : "Provider live test failed.",
2139
- metadata: typeof result === "boolean" ? void 0 : { action: result.action, warnings: result.warnings }
2140
- };
2141
- } catch (error) {
2142
- return {
2143
- id: "provider-live-test",
2144
- status: "unhealthy",
2145
- message: error instanceof Error ? error.message : "Provider live test failed."
2146
- };
1966
+ function previewInput(input) {
1967
+ return redactUnknown3(input);
1968
+ }
1969
+ function redactUnknown3(value) {
1970
+ if (Array.isArray(value)) return value.map(redactUnknown3);
1971
+ if (!value || typeof value !== "object") return value;
1972
+ const out = {};
1973
+ for (const [key, child] of Object.entries(value)) {
1974
+ if (/token|secret|password|authorization|api[_-]?key|credential/i.test(key)) {
1975
+ out[key] = "[REDACTED]";
1976
+ } else {
1977
+ out[key] = redactUnknown3(child);
1978
+ }
2147
1979
  }
1980
+ return out;
2148
1981
  }
2149
- function rollupHealthStatus(checks) {
2150
- if (checks.some((check) => check.status === "unhealthy")) return "unhealthy";
2151
- if (checks.some((check) => check.status === "degraded")) return "degraded";
2152
- if (checks.some((check) => check.status === "unknown")) return "unknown";
2153
- return "healthy";
1982
+
1983
+ // src/presets.ts
1984
+ function createPlatformIntegrationPolicyPreset(options = {}) {
1985
+ return new StaticIntegrationPolicyEngine({
1986
+ ...options,
1987
+ defaultReadEffect: "allow",
1988
+ defaultWriteEffect: options.allowWritesWithoutApproval ? "allow" : "require_approval",
1989
+ defaultDestructiveEffect: options.allowDestructiveActions ? "require_approval" : "deny",
1990
+ rules: [
1991
+ ...options.allowProviderPassthrough ? [] : [{
1992
+ id: "deny-provider-native-passthrough",
1993
+ action: "provider.http.request",
1994
+ effect: "deny",
1995
+ reason: "Provider-native passthrough is disabled by default. Promote the connector action or enable passthrough explicitly."
1996
+ }],
1997
+ ...options.rules ?? []
1998
+ ]
1999
+ });
2154
2000
  }
2155
2001
 
2156
2002
  // src/connectors/types.ts
@@ -5013,7 +4859,7 @@ var repoParams = {
5013
4859
  },
5014
4860
  required: ["owner", "repo"]
5015
4861
  };
5016
- var githubConnector = declarativeRestConnector({
4862
+ var githubConnector2 = declarativeRestConnector({
5017
4863
  kind: "github",
5018
4864
  displayName: "GitHub",
5019
4865
  description: "Search repositories/issues and create or update GitHub issues through a user-scoped token.",
@@ -5348,7 +5194,7 @@ var salesforceConnector = declarativeRestConnector({
5348
5194
  });
5349
5195
 
5350
5196
  // src/catalog.ts
5351
- var riskRank = {
5197
+ var riskRank2 = {
5352
5198
  read: 0,
5353
5199
  write: 1,
5354
5200
  destructive: 2
@@ -5371,7 +5217,7 @@ function buildIntegrationToolCatalog(connectors) {
5371
5217
  const tools = [];
5372
5218
  for (const connector of connectors) {
5373
5219
  for (const action of connector.actions) {
5374
- const tags = unique2([
5220
+ const tags = unique4([
5375
5221
  connector.id,
5376
5222
  connector.providerId,
5377
5223
  connector.title,
@@ -5410,7 +5256,7 @@ function searchIntegrationTools(catalog, query, filters = {}) {
5410
5256
  if (filters.connectorId && tool.connectorId !== filters.connectorId) return false;
5411
5257
  if (filters.category && tool.category !== filters.category) return false;
5412
5258
  if (filters.dataClass && tool.dataClass !== filters.dataClass) return false;
5413
- if (filters.maxRisk && riskRank[tool.risk] > riskRank[filters.maxRisk]) return false;
5259
+ if (filters.maxRisk && riskRank2[tool.risk] > riskRank2[filters.maxRisk]) return false;
5414
5260
  return true;
5415
5261
  });
5416
5262
  const scored = filtered.map((tool) => scoreTool(tool, terms));
@@ -5444,7 +5290,7 @@ function scoreTool(tool, terms) {
5444
5290
  }
5445
5291
  }
5446
5292
  if (tool.risk === "read") score += 0.25;
5447
- return { tool, score, matched: unique2(matched) };
5293
+ return { tool, score, matched: unique4(matched) };
5448
5294
  }
5449
5295
  function tokenize(value) {
5450
5296
  return value.toLowerCase().split(/[^a-z0-9]+/g).map((part) => part.trim()).filter(Boolean);
@@ -5455,110 +5301,10 @@ function encodeToolPart(value) {
5455
5301
  function decodeToolPart(value) {
5456
5302
  return Buffer.from(value.replace(/\./g, "_"), "base64url").toString("utf8");
5457
5303
  }
5458
- function unique2(values) {
5304
+ function unique4(values) {
5459
5305
  return [...new Set(values)];
5460
5306
  }
5461
5307
 
5462
- // src/policy.ts
5463
- import { randomUUID as randomUUID2 } from "crypto";
5464
- var StaticIntegrationPolicyEngine = class {
5465
- rules;
5466
- defaultReadEffect;
5467
- defaultWriteEffect;
5468
- defaultDestructiveEffect;
5469
- now;
5470
- constructor(options = {}) {
5471
- this.rules = options.rules ?? [];
5472
- this.defaultReadEffect = options.defaultReadEffect ?? "allow";
5473
- this.defaultWriteEffect = options.defaultWriteEffect ?? "require_approval";
5474
- this.defaultDestructiveEffect = options.defaultDestructiveEffect ?? "deny";
5475
- this.now = options.now ?? (() => /* @__PURE__ */ new Date());
5476
- }
5477
- decide(ctx) {
5478
- const action = ctx.action;
5479
- if (!action) return { decision: "deny", reason: "Integration action is missing from connector catalog." };
5480
- const matched = this.rules.find((rule) => ruleMatches(rule, ctx));
5481
- const effect = matched?.effect ?? this.defaultEffect(action.risk);
5482
- const reason = matched?.reason ?? defaultReason(effect, action.risk);
5483
- if (effect === "allow") return { decision: "allow", reason, metadata: matched ? { ruleId: matched.id } : void 0 };
5484
- if (effect === "deny") return { decision: "deny", reason, metadata: matched ? { ruleId: matched.id } : void 0 };
5485
- return {
5486
- decision: "require_approval",
5487
- reason,
5488
- approval: buildApprovalRequest(ctx, reason, this.now()),
5489
- metadata: matched ? { ruleId: matched.id } : void 0
5490
- };
5491
- }
5492
- defaultEffect(risk) {
5493
- if (risk === "read") return this.defaultReadEffect;
5494
- if (risk === "write") return this.defaultWriteEffect;
5495
- return this.defaultDestructiveEffect;
5496
- }
5497
- };
5498
- function createDefaultIntegrationPolicyEngine(options = {}) {
5499
- return new StaticIntegrationPolicyEngine(options);
5500
- }
5501
- function buildApprovalRequest(ctx, reason, requestedAt) {
5502
- if (!ctx.action) {
5503
- throw new Error("Cannot build approval request without an action descriptor.");
5504
- }
5505
- return {
5506
- id: `approval_${randomUUID2()}`,
5507
- connectionId: ctx.connection.id,
5508
- providerId: ctx.connection.providerId,
5509
- connectorId: ctx.connection.connectorId,
5510
- action: ctx.request.action,
5511
- actor: { type: ctx.subject.type, id: ctx.subject.id },
5512
- risk: ctx.action.risk,
5513
- dataClass: ctx.action.dataClass,
5514
- reason,
5515
- requestedAt: requestedAt.toISOString(),
5516
- inputPreview: previewInput(ctx.request.input)
5517
- };
5518
- }
5519
- function redactApprovalRequest(request) {
5520
- return {
5521
- ...request,
5522
- inputPreview: redactUnknown2(request.inputPreview)
5523
- };
5524
- }
5525
- function ruleMatches(rule, ctx) {
5526
- if (!ctx.action) return false;
5527
- if (rule.providerId && rule.providerId !== ctx.connection.providerId) return false;
5528
- if (rule.connectorId && rule.connectorId !== ctx.connection.connectorId) return false;
5529
- if (rule.action && rule.action !== ctx.request.action) return false;
5530
- if (rule.risk && rule.risk !== ctx.action.risk) return false;
5531
- if (rule.maxRisk && riskRank2(ctx.action.risk) > riskRank2(rule.maxRisk)) return false;
5532
- if (rule.dataClass && rule.dataClass !== ctx.action.dataClass) return false;
5533
- return true;
5534
- }
5535
- function riskRank2(risk) {
5536
- if (risk === "read") return 0;
5537
- if (risk === "write") return 1;
5538
- return 2;
5539
- }
5540
- function defaultReason(effect, risk) {
5541
- if (effect === "allow") return `${risk} integration action allowed by default policy.`;
5542
- if (effect === "deny") return `${risk} integration action denied by default policy.`;
5543
- return `${risk} integration action requires approval by default policy.`;
5544
- }
5545
- function previewInput(input) {
5546
- return redactUnknown2(input);
5547
- }
5548
- function redactUnknown2(value) {
5549
- if (Array.isArray(value)) return value.map(redactUnknown2);
5550
- if (!value || typeof value !== "object") return value;
5551
- const out = {};
5552
- for (const [key, child] of Object.entries(value)) {
5553
- if (/token|secret|password|authorization|api[_-]?key|credential/i.test(key)) {
5554
- out[key] = "[REDACTED]";
5555
- } else {
5556
- out[key] = redactUnknown2(child);
5557
- }
5558
- }
5559
- return out;
5560
- }
5561
-
5562
5308
  // src/sandbox.ts
5563
5309
  function buildIntegrationInvocationEnvelope(input) {
5564
5310
  const parsed = parseIntegrationToolName(input.toolName);
@@ -5617,13 +5363,13 @@ function redactInvocationEnvelope(envelope) {
5617
5363
  return {
5618
5364
  ...envelope,
5619
5365
  capabilityToken: "[REDACTED]",
5620
- input: redactUnknown3(envelope.input)
5366
+ input: redactUnknown4(envelope.input)
5621
5367
  };
5622
5368
  }
5623
5369
  function redactCapability(capability) {
5624
5370
  return {
5625
5371
  ...capability,
5626
- metadata: redactUnknown3(capability.metadata)
5372
+ metadata: redactUnknown4(capability.metadata)
5627
5373
  };
5628
5374
  }
5629
5375
  function normalizeIntegrationResult(result) {
@@ -5676,15 +5422,15 @@ var IntegrationSandboxHost = class {
5676
5422
  return dispatchIntegrationInvocation(envelope, this.options);
5677
5423
  }
5678
5424
  };
5679
- function redactUnknown3(value) {
5680
- if (Array.isArray(value)) return value.map(redactUnknown3);
5425
+ function redactUnknown4(value) {
5426
+ if (Array.isArray(value)) return value.map(redactUnknown4);
5681
5427
  if (!value || typeof value !== "object") return value;
5682
5428
  const out = {};
5683
5429
  for (const [key, child] of Object.entries(value)) {
5684
5430
  if (/token|secret|password|authorization|api[_-]?key|credential/i.test(key)) {
5685
5431
  out[key] = "[REDACTED]";
5686
5432
  } else {
5687
- out[key] = redactUnknown3(child);
5433
+ out[key] = redactUnknown4(child);
5688
5434
  }
5689
5435
  }
5690
5436
  return out;
@@ -5750,7 +5496,7 @@ function importMcpConnector(catalog, options) {
5750
5496
  }));
5751
5497
  }
5752
5498
  function connectorFromActions(options, actions) {
5753
- const scopes = unique3([
5499
+ const scopes = unique5([
5754
5500
  ...options.scopes ?? [],
5755
5501
  ...actions.flatMap((action) => action.requiredScopes)
5756
5502
  ]);
@@ -5782,7 +5528,7 @@ function riskFromMcpTool(tool, fallback) {
5782
5528
  }
5783
5529
  function scopesFromOpenApiOperation(operation, fallback) {
5784
5530
  const scopes = (operation.security ?? []).flatMap((entry) => Object.values(entry).flat());
5785
- return unique3(scopes.length > 0 ? scopes : fallback);
5531
+ return unique5(scopes.length > 0 ? scopes : fallback);
5786
5532
  }
5787
5533
  function openApiInputSchema(path, method, operation) {
5788
5534
  return {
@@ -5802,7 +5548,7 @@ function titleFromId(id) {
5802
5548
  function isObject(value) {
5803
5549
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
5804
5550
  }
5805
- function unique3(values) {
5551
+ function unique5(values) {
5806
5552
  return [...new Set(values)];
5807
5553
  }
5808
5554
 
@@ -5853,7 +5599,7 @@ function normalizeGatewayCatalog(entries, options) {
5853
5599
  title,
5854
5600
  category: normalizeCategory(entry.category),
5855
5601
  auth: normalizeAuth(entry.auth),
5856
- scopes: unique4([
5602
+ scopes: unique6([
5857
5603
  ...entry.scopes ?? [],
5858
5604
  ...actions.flatMap((action) => action.requiredScopes)
5859
5605
  ]),
@@ -5883,7 +5629,7 @@ function normalizeActions(actions, fallbackScopes) {
5883
5629
  id,
5884
5630
  title: action.title ?? action.name ?? titleFromId2(id),
5885
5631
  risk: normalizeRisk(action.risk),
5886
- requiredScopes: unique4([
5632
+ requiredScopes: unique6([
5887
5633
  ...action.requiredScopes ?? [],
5888
5634
  ...action.scopes ?? [],
5889
5635
  ...action.requiredScopes?.length || action.scopes?.length ? [] : fallbackScopes
@@ -5897,21 +5643,21 @@ function normalizeActions(actions, fallbackScopes) {
5897
5643
  }).filter((action) => action.id);
5898
5644
  }
5899
5645
  function normalizeTriggers(triggers, fallbackScopes) {
5900
- const normalized = triggers.map((trigger) => {
5901
- const id = slug2(trigger.id ?? trigger.key ?? trigger.name ?? trigger.title ?? "");
5646
+ const normalized = triggers.map((trigger2) => {
5647
+ const id = slug2(trigger2.id ?? trigger2.key ?? trigger2.name ?? trigger2.title ?? "");
5902
5648
  return {
5903
5649
  id,
5904
- title: trigger.title ?? trigger.name ?? titleFromId2(id),
5905
- requiredScopes: unique4([
5906
- ...trigger.requiredScopes ?? [],
5907
- ...trigger.scopes ?? [],
5908
- ...trigger.requiredScopes?.length || trigger.scopes?.length ? [] : fallbackScopes
5650
+ title: trigger2.title ?? trigger2.name ?? titleFromId2(id),
5651
+ requiredScopes: unique6([
5652
+ ...trigger2.requiredScopes ?? [],
5653
+ ...trigger2.scopes ?? [],
5654
+ ...trigger2.requiredScopes?.length || trigger2.scopes?.length ? [] : fallbackScopes
5909
5655
  ]),
5910
- dataClass: normalizeDataClass(trigger.dataClass),
5911
- description: trigger.description,
5912
- payloadSchema: trigger.payloadSchema
5656
+ dataClass: normalizeDataClass(trigger2.dataClass),
5657
+ description: trigger2.description,
5658
+ payloadSchema: trigger2.payloadSchema
5913
5659
  };
5914
- }).filter((trigger) => trigger.id);
5660
+ }).filter((trigger2) => trigger2.id);
5915
5661
  return normalized.length > 0 ? normalized : void 0;
5916
5662
  }
5917
5663
  function defaultActionsFor(category, scopes) {
@@ -5991,7 +5737,7 @@ function slug2(value) {
5991
5737
  function titleFromId2(id) {
5992
5738
  return id.split("-").filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
5993
5739
  }
5994
- function unique4(values) {
5740
+ function unique6(values) {
5995
5741
  return [...new Set(values)];
5996
5742
  }
5997
5743
 
@@ -6079,7 +5825,7 @@ var IntegrationRuntime = class {
6079
5825
  const connector = {
6080
5826
  ...entry.connector,
6081
5827
  actions: entry.connector.actions.filter((action) => grant.allowedActions.includes(action.id)),
6082
- triggers: entry.connector.triggers?.filter((trigger) => grant.allowedTriggers.includes(trigger.id)),
5828
+ triggers: entry.connector.triggers?.filter((trigger2) => grant.allowedTriggers.includes(trigger2.id)),
6083
5829
  scopes: entry.connector.scopes.filter((scope) => grant.scopes.includes(scope))
6084
5830
  };
6085
5831
  const capability = await this.hub.issueCapability({
@@ -6173,32 +5919,32 @@ function missing(requirement, status, message, connector, registryEntry2) {
6173
5919
  }
6174
5920
  function requiredActions(requirement, connector) {
6175
5921
  if (requirement.mode === "trigger") return [];
6176
- if (requirement.requiredActions?.length) return unique5(requirement.requiredActions);
5922
+ if (requirement.requiredActions?.length) return unique7(requirement.requiredActions);
6177
5923
  const actions = connector.actions.filter((action) => {
6178
5924
  if (requirement.mode === "read") return action.risk === "read";
6179
5925
  if (requirement.mode === "write") return action.risk !== "read";
6180
5926
  return false;
6181
5927
  });
6182
- return unique5(actions.map((action) => action.id));
5928
+ return unique7(actions.map((action) => action.id));
6183
5929
  }
6184
5930
  function requiredTriggers(requirement, connector) {
6185
- if (requirement.requiredTriggers?.length) return unique5(requirement.requiredTriggers);
5931
+ if (requirement.requiredTriggers?.length) return unique7(requirement.requiredTriggers);
6186
5932
  if (requirement.mode !== "trigger") return [];
6187
- return unique5((connector.triggers ?? []).map((trigger) => trigger.id));
5933
+ return unique7((connector.triggers ?? []).map((trigger2) => trigger2.id));
6188
5934
  }
6189
5935
  function requiredScopes(requirement, connector) {
6190
- if (requirement.requiredScopes?.length) return unique5(requirement.requiredScopes);
5936
+ if (requirement.requiredScopes?.length) return unique7(requirement.requiredScopes);
6191
5937
  const actionIds = new Set(requiredActions(requirement, connector));
6192
5938
  const triggerIds = new Set(requiredTriggers(requirement, connector));
6193
- return unique5([
5939
+ return unique7([
6194
5940
  ...connector.actions.filter((action) => actionIds.has(action.id)).flatMap((action) => action.requiredScopes),
6195
- ...(connector.triggers ?? []).filter((trigger) => triggerIds.has(trigger.id)).flatMap((trigger) => trigger.requiredScopes)
5941
+ ...(connector.triggers ?? []).filter((trigger2) => triggerIds.has(trigger2.id)).flatMap((trigger2) => trigger2.requiredScopes)
6196
5942
  ]);
6197
5943
  }
6198
5944
  function sameActor(a, b) {
6199
5945
  return a.type === b.type && a.id === b.id;
6200
5946
  }
6201
- function unique5(values) {
5947
+ function unique7(values) {
6202
5948
  return [...new Set(values)];
6203
5949
  }
6204
5950
 
@@ -6284,123 +6030,6 @@ function sameActor2(a, b) {
6284
6030
  return a.type === b.type && a.id === b.id;
6285
6031
  }
6286
6032
 
6287
- // src/specs/types.ts
6288
- function specAuthToConnectorAuth(auth) {
6289
- if (auth.mode === "api_key") return "api_key";
6290
- if (auth.mode === "oauth2") return "oauth2";
6291
- if (auth.mode === "none") return "none";
6292
- return "custom";
6293
- }
6294
-
6295
- // src/specs/renderers.ts
6296
- function renderConsoleSteps(spec, options) {
6297
- const redirectUri = renderRedirectUri(spec, options);
6298
- return spec.setup.consoleSteps.map((step) => ({
6299
- ...step,
6300
- detail: renderTemplate(step.detail, spec, options, redirectUri),
6301
- copyValue: step.copyValue ? renderTemplate(step.copyValue, spec, options, redirectUri) : void 0
6302
- }));
6303
- }
6304
- function renderRunbookMarkdown(spec, options) {
6305
- const steps = renderConsoleSteps(spec, options);
6306
- const lines = [
6307
- `# ${spec.title} Integration Setup`,
6308
- "",
6309
- `- Kind: \`${spec.kind}\``,
6310
- `- Status: \`${spec.status}\``,
6311
- `- Auth: \`${spec.auth.mode}\``,
6312
- `- Family: \`${spec.family}\``
6313
- ];
6314
- if (spec.setup.consoleUrl) lines.push(`- Console: ${spec.setup.consoleUrl}`);
6315
- if (spec.setup.redirectUriTemplate) lines.push(`- Redirect URI: \`${renderRedirectUri(spec, options)}\``);
6316
- lines.push("", "## Credentials", "");
6317
- for (const field of spec.setup.credentialFields) {
6318
- lines.push(`- ${field.secret ? "[secret] " : ""}${field.label}${field.env ? ` (\`${field.env}\`)` : ""}: ${field.description}`);
6319
- }
6320
- lines.push("", "## Permissions", "");
6321
- for (const permission of spec.permissions) {
6322
- lines.push(`- \`${permission.normalized}\`: ${permission.providerScopes.length ? permission.providerScopes.map((scope) => `\`${scope}\``).join(", ") : "no provider scope"} - ${permission.reason}`);
6323
- }
6324
- lines.push("", "## Console Steps", "");
6325
- for (const [i, step] of steps.entries()) {
6326
- lines.push(`${i + 1}. ${step.title}: ${step.detail}`);
6327
- }
6328
- if (spec.setup.knownQuirks?.length) {
6329
- lines.push("", "## Known Quirks", "");
6330
- for (const quirk of spec.setup.knownQuirks) lines.push(`- ${quirk.severity}: ${quirk.message}`);
6331
- }
6332
- return `${lines.join("\n")}
6333
- `;
6334
- }
6335
- function renderAgentToolDescription(spec) {
6336
- const hints = spec.plannerHints;
6337
- const useFor = hints?.useFor?.length ? `Use for ${hints.useFor.join(", ")}.` : `Use for ${spec.title} workflows.`;
6338
- const risk = hints ? `Freshness: ${hints.dataFreshness}. Write risk: ${hints.writeRisk}.` : "";
6339
- return `${spec.title} (${spec.kind}). ${useFor} ${risk}`.trim();
6340
- }
6341
- function buildHealthcheckPlan(spec) {
6342
- const healthcheck = spec.setup.healthcheck ?? { id: `${spec.kind}.static`, level: "static", description: "No healthcheck defined." };
6343
- const requires = [];
6344
- if (healthcheck.level === "connection") requires.push("connection_credentials");
6345
- if (healthcheck.level === "client_config" && spec.auth.mode === "oauth2") requires.push("client_id", "client_secret");
6346
- if (spec.auth.mode === "api_key") requires.push("api_key");
6347
- if (spec.auth.mode === "hmac") requires.push("hmac_secret");
6348
- return {
6349
- kind: spec.kind,
6350
- healthcheck,
6351
- requires,
6352
- message: healthcheck.description
6353
- };
6354
- }
6355
- function renderTemplate(template, spec, options, redirectUri) {
6356
- return template.replaceAll("{host}", options.host).replaceAll("{kind}", spec.kind).replaceAll("{redirectUri}", redirectUri ?? renderRedirectUri(spec, options));
6357
- }
6358
- function renderRedirectUri(spec, options) {
6359
- return (options.callbackPath ?? spec.setup.redirectUriTemplate ?? "").replaceAll("{host}", options.host).replaceAll("{kind}", spec.kind);
6360
- }
6361
- function consoleStepsToText(steps) {
6362
- return steps.map((step, index) => `${index + 1}. ${step.title}: ${step.detail}`).join("\n");
6363
- }
6364
-
6365
- // src/specs/validation.ts
6366
- function validateIntegrationSpec(spec) {
6367
- const issues = [];
6368
- if (!spec.kind.trim()) issues.push({ path: "kind", message: "kind is required" });
6369
- if (!spec.title.trim()) issues.push({ path: "title", message: "title is required" });
6370
- if (!spec.actions.length) issues.push({ path: "actions", message: "at least one action is required" });
6371
- if (!spec.permissions.length) issues.push({ path: "permissions", message: "at least one permission is required" });
6372
- if (spec.auth.mode === "oauth2") {
6373
- if (!spec.auth.authorizationUrl) issues.push({ path: "auth.authorizationUrl", message: "authorizationUrl is required" });
6374
- if (!spec.auth.tokenUrl) issues.push({ path: "auth.tokenUrl", message: "tokenUrl is required" });
6375
- if (!spec.auth.redirectUriTemplate) issues.push({ path: "auth.redirectUriTemplate", message: "redirectUriTemplate is required" });
6376
- }
6377
- const actionIds = /* @__PURE__ */ new Set();
6378
- for (const [index, action] of spec.actions.entries()) {
6379
- if (actionIds.has(action.id)) issues.push({ path: `actions[${index}].id`, message: `duplicate action id ${action.id}` });
6380
- actionIds.add(action.id);
6381
- }
6382
- return { ok: issues.length === 0, issues };
6383
- }
6384
- function assertValidIntegrationSpec(spec) {
6385
- const result = validateIntegrationSpec(spec);
6386
- if (!result.ok) {
6387
- throw new Error(`Invalid integration spec ${spec.kind}: ${result.issues.map((i) => `${i.path}: ${i.message}`).join("; ")}`);
6388
- }
6389
- }
6390
- function validateCredentialFormat(field, value) {
6391
- if (!value.trim()) return { ok: false, field: field.label, message: `${field.label} is required` };
6392
- if (field.regex && !new RegExp(field.regex).test(value)) {
6393
- return { ok: false, field: field.label, message: `${field.label} does not match expected format` };
6394
- }
6395
- return { ok: true, field: field.label };
6396
- }
6397
- function validateCredentialSet(spec, values) {
6398
- return spec.setup.credentialFields.map((field) => {
6399
- const key = field.env ?? field.label;
6400
- return validateCredentialFormat(field, values[key] ?? "");
6401
- });
6402
- }
6403
-
6404
6033
  // src/index.ts
6405
6034
  var IntegrationError = class extends Error {
6406
6035
  constructor(message, code) {
@@ -6485,8 +6114,8 @@ var IntegrationHub = class {
6485
6114
  id: `cap_${randomUUID3()}`,
6486
6115
  subject: request.subject,
6487
6116
  connectionId: request.connectionId,
6488
- scopes: unique6(request.scopes),
6489
- allowedActions: unique6(request.allowedActions),
6117
+ scopes: unique8(request.scopes),
6118
+ allowedActions: unique8(request.allowedActions),
6490
6119
  issuedAt: now.toISOString(),
6491
6120
  expiresAt: new Date(now.getTime() + request.ttlMs).toISOString(),
6492
6121
  metadata: request.metadata
@@ -6539,18 +6168,18 @@ var IntegrationHub = class {
6539
6168
  }
6540
6169
  return proceed();
6541
6170
  }
6542
- async subscribeTrigger(connectionId, trigger, targetUrl) {
6171
+ async subscribeTrigger(connectionId, trigger2, targetUrl) {
6543
6172
  const connection = await this.requireConnection(connectionId);
6544
6173
  this.assertConnectionActive(connection);
6545
6174
  const provider = this.requireProvider(connection.providerId);
6546
6175
  const connector = await this.requireConnector(provider, connection.connectorId);
6547
- const spec = connector.triggers?.find((candidate) => candidate.id === trigger);
6548
- if (!spec) throw new IntegrationError(`Trigger ${trigger} is not defined by connector ${connector.id}.`, "action_not_found");
6176
+ const spec = connector.triggers?.find((candidate) => candidate.id === trigger2);
6177
+ if (!spec) throw new IntegrationError(`Trigger ${trigger2} is not defined by connector ${connector.id}.`, "action_not_found");
6549
6178
  assertScopes(connection, spec.requiredScopes);
6550
6179
  if (!provider.subscribeTrigger) {
6551
6180
  throw new IntegrationError(`Provider ${provider.id} does not support triggers.`, "auth_not_supported");
6552
6181
  }
6553
- return provider.subscribeTrigger(connection, trigger, targetUrl);
6182
+ return provider.subscribeTrigger(connection, trigger2, targetUrl);
6554
6183
  }
6555
6184
  requireProvider(providerId) {
6556
6185
  const provider = this.providers.get(providerId);
@@ -6635,10 +6264,10 @@ function createMockIntegrationProvider(options = {}) {
6635
6264
  action: request.action,
6636
6265
  output: { echo: request.input ?? null }
6637
6266
  },
6638
- subscribeTrigger: (connection, trigger, targetUrl) => ({
6639
- id: `sub_${connection.id}_${trigger}`,
6267
+ subscribeTrigger: (connection, trigger2, targetUrl) => ({
6268
+ id: `sub_${connection.id}_${trigger2}`,
6640
6269
  connectionId: connection.id,
6641
- trigger,
6270
+ trigger: trigger2,
6642
6271
  targetUrl,
6643
6272
  status: "active",
6644
6273
  createdAt: (/* @__PURE__ */ new Date(0)).toISOString()
@@ -6666,10 +6295,10 @@ function createHttpIntegrationProvider(options) {
6666
6295
  request
6667
6296
  }, options.bearer);
6668
6297
  },
6669
- async subscribeTrigger(connection, trigger, targetUrl) {
6298
+ async subscribeTrigger(connection, trigger2, targetUrl) {
6670
6299
  return postJson(fetcher, `${baseUrl}/triggers/subscribe`, {
6671
6300
  connection,
6672
- trigger,
6301
+ trigger: trigger2,
6673
6302
  targetUrl
6674
6303
  }, options.bearer);
6675
6304
  },
@@ -6732,12 +6361,13 @@ function base64UrlEncode(value) {
6732
6361
  function base64UrlDecode(value) {
6733
6362
  return Buffer.from(value, "base64url").toString("utf8");
6734
6363
  }
6735
- function unique6(values) {
6364
+ function unique8(values) {
6736
6365
  return [...new Set(values)];
6737
6366
  }
6738
6367
  export {
6739
6368
  ACTIVEPIECES_OVERRIDES,
6740
6369
  ApprovalBackedPolicyEngine,
6370
+ CANONICAL_INTEGRATION_ACTIONS,
6741
6371
  CredentialsExpired,
6742
6372
  DEFAULT_INTEGRATION_BRIDGE_ENV,
6743
6373
  DEFAULT_SIGNATURE_TOLERANCE_SECONDS,
@@ -6756,17 +6386,22 @@ export {
6756
6386
  IntegrationError,
6757
6387
  IntegrationHub,
6758
6388
  IntegrationRuntime,
6389
+ IntegrationRuntimeError,
6759
6390
  IntegrationSandboxHost,
6760
6391
  IntegrationWorkflowRuntime,
6392
+ PROVIDER_PASSTHROUGH_ACTION,
6761
6393
  ResourceContention,
6762
6394
  StaticIntegrationPolicyEngine,
6395
+ TangleIntegrationsClient,
6763
6396
  _resetPendingFlowsForTests,
6764
6397
  airtableConnector,
6765
6398
  asanaConnector,
6766
6399
  assertValidConnectorManifest,
6400
+ assertValidIntegrationManifest,
6767
6401
  assertValidIntegrationSpec,
6768
6402
  buildActivepiecesConnectors,
6769
6403
  buildApprovalRequest,
6404
+ buildCanonicalLaunchConnectors,
6770
6405
  buildDefaultIntegrationRegistry,
6771
6406
  buildHealthcheckPlan,
6772
6407
  buildIntegrationBridgeEnvironment,
@@ -6774,6 +6409,8 @@ export {
6774
6409
  buildIntegrationCoverageConnectors,
6775
6410
  buildIntegrationInvocationEnvelope,
6776
6411
  buildIntegrationToolCatalog,
6412
+ calendarExercisePlannerManifest,
6413
+ canonicalActionConnectorId,
6777
6414
  canonicalConnectorId,
6778
6415
  composeIntegrationRegistry,
6779
6416
  consoleStepsToText,
@@ -6791,16 +6428,19 @@ export {
6791
6428
  createIntegrationRuntime,
6792
6429
  createIntegrationWorkflowRuntime,
6793
6430
  createMockIntegrationProvider,
6431
+ createPlatformIntegrationPolicyPreset,
6432
+ createTangleIntegrationsClient,
6794
6433
  declarativeRestConnector,
6795
6434
  decodeIntegrationBridgePayload,
6796
6435
  dispatchIntegrationInvocation,
6797
6436
  encodeIntegrationBridgePayload,
6798
6437
  exchangeAuthorizationCode,
6438
+ explainMissingRequirements,
6799
6439
  firstHeader,
6800
6440
  getActivepiecesOverride,
6801
6441
  getIntegrationFamily,
6802
6442
  getIntegrationSpec,
6803
- githubConnector,
6443
+ githubConnector2 as githubConnector,
6804
6444
  gitlabConnector,
6805
6445
  googleCalendar,
6806
6446
  googleSheets,
@@ -6809,6 +6449,7 @@ export {
6809
6449
  importGraphqlConnector,
6810
6450
  importMcpConnector,
6811
6451
  importOpenApiConnector,
6452
+ inferIntegrationManifestFromTools,
6812
6453
  inferIntegrationSupportTier,
6813
6454
  integrationCoverageChecklistMarkdown,
6814
6455
  integrationSpecToConnector,
@@ -6821,6 +6462,7 @@ export {
6821
6462
  manifestToConnector,
6822
6463
  microsoftCalendar,
6823
6464
  normalizeGatewayCatalog,
6465
+ normalizeIntegrationError,
6824
6466
  normalizeIntegrationResult,
6825
6467
  notionDatabase,
6826
6468
  parseIntegrationBridgeEnvironment,
@@ -6833,6 +6475,8 @@ export {
6833
6475
  redactInvocationEnvelope,
6834
6476
  refreshAccessToken,
6835
6477
  renderAgentToolDescription,
6478
+ renderApprovalCopy,
6479
+ renderConsentSummary,
6836
6480
  renderConsoleSteps,
6837
6481
  renderRunbookMarkdown,
6838
6482
  resolveConnectionCredentials,
@@ -6849,6 +6493,7 @@ export {
6849
6493
  slackEventsConnector,
6850
6494
  specAuthToConnectorAuth,
6851
6495
  startOAuthFlow,
6496
+ statusForCode,
6852
6497
  storedEventToTriggerEvent,
6853
6498
  stripePackConnector,
6854
6499
  stripeWebhookReceiverConnector,
@@ -6859,7 +6504,9 @@ export {
6859
6504
  validateCredentialFormat,
6860
6505
  validateCredentialSet,
6861
6506
  validateIntegrationInvocationEnvelope,
6507
+ validateIntegrationManifest,
6862
6508
  validateIntegrationSpec,
6509
+ validateProviderPassthroughRequest,
6863
6510
  verifyCapabilityToken,
6864
6511
  verifyHmacSignature,
6865
6512
  verifySlackSignature,