(
integrationRow: IntegrationRow,
ref: ConnectionRef,
mode: "explicit" | "background" = "explicit",
)
| 2493 | result.incompleteReason ?? "plugin returned an incomplete tool catalog"; |
| 2494 | |
| 2495 | const produceConnectionTools = ( |
| 2496 | integrationRow: IntegrationRow, |
| 2497 | ref: ConnectionRef, |
| 2498 | mode: "explicit" | "background" = "explicit", |
| 2499 | ): Effect.Effect<readonly Tool[], IntegrationNotFoundError | StorageFailure> => |
| 2500 | Effect.gen(function* () { |
| 2501 | const runtime = runtimes.get(integrationRow.plugin_id); |
| 2502 | const keys = yield* Effect.try({ |
| 2503 | try: () => ownedKeys(ref.owner), |
| 2504 | catch: (cause) => storageFailureFromUnknown("invalid owner", cause), |
| 2505 | }); |
| 2506 | const owner = ref.owner; |
| 2507 | const where = (b: AnyCb) => |
| 2508 | b.and( |
| 2509 | byOwner(owner)(b), |
| 2510 | b("integration", "=", String(ref.integration)), |
| 2511 | b("connection", "=", String(ref.name)), |
| 2512 | ); |
| 2513 | const connectionWhere = (b: AnyCb) => |
| 2514 | b.and( |
| 2515 | byOwner(owner)(b), |
| 2516 | b("integration", "=", String(ref.integration)), |
| 2517 | b("name", "=", String(ref.name)), |
| 2518 | ); |
| 2519 | const isToolSyncHealth = (health: HealthCheckResult | null): boolean => |
| 2520 | health?.detail?.startsWith(toolSyncHealthDetailPrefix) === true; |
| 2521 | const syncedSet = (row: ConnectionRow | null) => { |
| 2522 | const health = row ? Option.getOrNull(decodeLastHealth(row.last_health)) : null; |
| 2523 | return isToolSyncHealth(health) |
| 2524 | ? { tools_synced_at: Date.now(), last_health: null, updated_at: new Date() } |
| 2525 | : { tools_synced_at: Date.now() }; |
| 2526 | }; |
| 2527 | // Every exit stamps the sync time — including the cleanup paths that |
| 2528 | // produce zero tools — so the stale-catalog check (`config_revised_at` |
| 2529 | // vs `tools_synced_at`) doesn't re-attempt this connection per read. |
| 2530 | // Successful syncs also clear stale sync-failure health records, while |
| 2531 | // preserving genuine health-check outcomes. |
| 2532 | const stampSynced = (row: ConnectionRow | null) => |
| 2533 | core.updateMany("connection", { |
| 2534 | where: connectionWhere, |
| 2535 | set: syncedSet(row), |
| 2536 | }); |
| 2537 | const stampSyncedWithHealth = (reason: string) => |
| 2538 | core.updateMany("connection", { |
| 2539 | where: connectionWhere, |
| 2540 | set: { |
| 2541 | tools_synced_at: Date.now(), |
| 2542 | last_health: toolSyncHealth(reason), |
| 2543 | updated_at: new Date(), |
| 2544 | }, |
| 2545 | }); |
| 2546 | |
| 2547 | // Defense in depth (and cleanup for rows created before the create-time |
| 2548 | // guard, or emptied by an external edit): a credentialed non-OAuth |
| 2549 | // connection with no bound credential inputs can never resolve a value, |
| 2550 | // so never advertise tools for it — every call would fail with |
| 2551 | // `connection_value_missing`. OAuth connections resolve via refresh and |
| 2552 | // carry their token outside `item_ids`; no-auth (`"none"` template) |
no test coverage detected