Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ Postmortems (Redis/queue overload, infra upgrade — March 2026) and product int

## Gotchas
- **`DISCORD_ON_CALL_WEBHOOK` is the single repo secret behind every on-call Discord notification** — cloud production deploys (`continuous-delivery-cloud.yml`), self-hosted releases (`release-self-hosted.yml`), and release-pieces failure alerts all post through it. Rotating it repoints all of them at once; there is no per-workflow webhook. Self-hosted release notifications skip `-rc` tags unless `publish_rc_release` is set, mirroring the release-drafter condition so the message never links a release page that doesn't exist.
- **Docs media lives on the CDN, never in the repo — and videos and images sit on two different paths.** A video embeds as `<video src="https://cdn.activepieces.com/videos/docs/<Exact-File-Name>.mp4" controls />`, an image as `![Alt](https://cdn.activepieces.com/assets/<exact-file-name>.png)`. Videos are namespaced under `videos/docs/`; images are **flat** under `assets/` with no `docs/` segment, so don't infer one path from the other — check the URL you actually mean. Both mirror the filename verbatim, so an upload is fetchable with no config change. Committing the binary instead bloats every clone forever (`docs/videos/` reached 63 MB and `docs/images/` 1.6 MB before both were removed in Sept 2026) since git history keeps the blob even after a delete. Curl each URL for a 200 before deleting a local copy: `docs/` also carries stale `.mdx` references to images that never existed in git history at all, and rewriting one of those to a CDN URL turns a visibly-missing asset into a confident 404.
2 changes: 2 additions & 0 deletions brain/knowledge/pieces-engine/building-pieces.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ Authentication, triggers (polling/webhook), properties + validation, flow contro
- **Pass the whole `context` to `pollingHelper`, never `{ store, auth, propsValue }`.** The destructured form is the dominant shape in the repo (306 of 403 `onEnable` call sites) and it type-checks, so it reads as idiomatic — but the helper's param type is wider than those three fields, and TypeScript only rejects excess properties, never missing optional ones. So each field added to the polling context is silently absent in every destructuring trigger. `context.isRepublish` is the first one that changes behaviour: `pollingHelper.onEnable` uses it to keep the existing `lastPoll`/`lastItem` instead of resetting to now, so a destructuring trigger still drops every event between its last poll and a republish ([triggers.md](../flows-execution/triggers.md) has the platform-side thread). Scaffolding (`npm run cli triggers create`), `docs/build-pieces/`, and the piece-builder skill all pass `context`, so new triggers are fine — the trap is copying from a neighbouring piece, since the wrong shape is the majority there. The legacy sites are being fixed **on touch** rather than by one repo-wide codemod: whoever edits a piece switches that piece's calls over, which rides an existing version bump and rebuild instead of forcing one on ~300 pieces nobody is running.
- **Porting postgres `new-row.ts` to another SQL piece: the `LIMIT 5` is cold-start only — do not carry it onto the resume branch.** `constructQuery` (`postgres/src/lib/triggers/new-row.ts:41`) has two shapes, and the asymmetry between them is load-bearing: the no-checkpoint branch seeds with `ORDER BY %I DESC LIMIT 5` (`:46,48`), while the resume branch is deliberately **unbounded** — `WHERE %I >= %L ORDER BY %I DESC`, no LIMIT (`:58,60`). It has to be, because `DedupeStrategy.LAST_ITEM` (`:17`) recovers the checkpoint by scanning *the page it just fetched* (`pieces/common/src/lib/polling/index.ts:99`, `items.findIndex((f) => f.id === lastItemId)`) and emits everything ahead of it. Bound the resume page and the checkpoint row can fall off the end, where `findIndex → -1` is read as "no checkpoint" and the entire page re-emits ([triggers.md](../flows-execution/triggers.md) has the same mechanic from the republish side). So a literal `LIMIT 5` → `TOP (5)` is a behaviour change, not a dialect translation — and the moment you *do* want a bounded resume page you are off `pollingHelper` altogether and owe a keyset cursor that carries its position in the store instead of recovering it by scanning: `microsoft-sql-server/src/lib/common/cursor.ts` is the worked example (`TOP (@limit)` on every page, versioned cursor, explicit tiebreaker key). Two more sharp edges if you copy this template: the item id is `orderValue + '|' + md5(JSON.stringify(row))` (`:24-28`), so **any edit to the checkpoint row changes its id and invalidates the checkpoint**, and `lastItem.split('|')[0]` (`:42`) truncates any order value containing a literal `|` — fine for timestamps and serial ids, wrong for ordering on a text column.
- **Streaming a file *into* a piece is `Property.File({ streaming: true })`.** It resolves to an `ApStreamingFile` with `body: Readable` (pieces-framework ≥ 0.35.0, [000014](../../decisions/000014-streaming-file-inputs-resolve-to-a-lazy-apstreamingfile.md)) and accepts a URL, a base64 data URL, the builder's file picker, or a previous step's file — a strict superset of a URL text field, with the fetch owned by the engine. `amazon-s3/upload-file.ts` and `subflows/stream-csv-to-flow.ts` are the references. Three things to know: the engine's `fileProcessor` swallows fetch failures and returns `null`, which for a `required: true` prop surfaces as the confusing `Expected file url or base64 with mimeType` validation error rather than a fetch error (so no `isNil` guard in your `run()` is needed — the action never starts); the engine's fetch has **no timeout**, so a source that connects then stalls burns `FLOW_TIMEOUT_SECONDS`; and `.pipe()` does not forward `'error'`, so you still need `file.body.on('error', ...)` or a mid-stream network drop becomes an uncaught exception in the sandbox.
- **A piece's `validate()` catch block swallows engine failures as invalid connections unless you explicitly re-throw.** The auth server context hands `validate()` a `mintOidcToken({ audience })` callback whose engine-side implementation calls the internal `/v1/worker/oidc-token` endpoint; when that endpoint 5xxs, the engine throws a `PieceServerContextError` (from `@activepieces/pieces-framework`). It looks like any other `Error`, so a naive `try { await getTemporaryCredentials(...) } catch (e) { return { valid: false, error: format(e) } }` reports the platform outage as `INVALID_APP_CONNECTION` — user sees "your connection is bad", oncall gets no page. The contract every OIDC piece must follow is `if (isPieceServerContextError(error)) throw error;` before the catch's return; `executeValidateAuth` (`packages/server/engine/src/lib/helper/piece-helper.ts`) re-wraps it as `EngineGenericError`, which `tryCatchAndThrowOnEngineError` routes as `ExecutionErrorType.ENGINE` and pages. The reference implementations are the four AWS OIDC pieces (`amazon-bedrock`, `aws-bedrock`, `amazon-s3`, `amazon-secrets-manager`) — same shape in all of them. Runtime paths (executing a step, refreshing a token) intentionally do NOT go through this class — a runtime OIDC failure fails one step as a user-level error, which is correct because a persistent platform issue would show up across many flows and be caught operationally.
- **AWS region strings flow into the STS endpoint hostname.** The `@aws-sdk/client-sts` builds the STS endpoint URL by interpolating `sts.<region>.amazonaws.com`, and does not itself check the shape. A crafted region like `us-east-1.evil.com` redirects the STS call to an attacker-controlled host — a real SSRF-shaped issue since the piece runs in the engine sandbox with outbound network access and the JWT it's about to send is a valid, platform-signed OIDC token. Validate the region against `/^[a-z]{2}(-[a-z]+)+-\d$/` **before** any code that touches it (before minting the OIDC token, before building the STS client, before the cache key). All four AWS OIDC pieces enforce this in `getTemporaryCredentials`. The same shape applies to any AWS SDK client built with a user-controlled region.
- **Streaming only removes *our* memory ceiling — check the destination's per-request cap before calling an upload action fixed.** A body that streams cleanly out of the sandbox still gets rejected whole by the API: Dropbox's `/2/files/upload` answers `409 {".tag": "payload_too_large"}` above 150 MB, Graph's simple `PUT …/content` above 250 MB. The tell that it's the service and not us is the shape — an endpoint-specific 409 with a documented error tag, and an axios/undici request echo whose `body` is just a `_readableState` blob (our stream, sent fine). The fix is a chunked upload session, not a bigger buffer: `dropbox/upload-file.ts` and `microsoft-onedrive/upload-file.ts` are the references, both chunking through the shared `streamUtils.readChunks({ readable, chunkSize })` from `@activepieces/pieces-common` — reuse it rather than writing a third stream chunker. Two rules that fall out of doing it: **route unknown-size sources through the session too** (`size` is best-effort and absent on chunked or compressed sources, so you cannot prove they fit — and the old fallback of buffering to learn the size is the OOM this streaming work exists to remove), and keep the chunk size a multiple of the service's preferred unit (4 MiB for Dropbox, 320 KiB for OneDrive). Chunk bodies are `Buffer`s, so unlike a one-shot stream body they keep `httpClient`'s retries. **Whether you can chunk an unknown-size source at all depends on how the session addresses its parts:** Dropbox's is offset-based (`cursor.offset`, no total ever declared) so it streams straight through, while Graph's wants the file's total length in every fragment's `Content-Range` — so `microsoft-sharepoint` and `microsoft-onedrive` must `readableToBuffer` once to learn the length, then re-wrap with `Readable.from` so both branches still take a stream. That buffer is the OOM this work removes, so it is a last resort, not the pattern: reach for the offset-based session whenever the API offers one. SharePoint's cap is generous enough (250 MB one-shot vs OneDrive's 4 MiB) that the buffer only ever runs for a size-less source.
- **On Windows, a new action/trigger name (or any metadata-shape change) needs the dev server process killed, not restarted.** `clearPieceModuleCache` — the only thing that busts the CommonJS `require()` cache backing dev piece metadata — is called exclusively from the chokidar watcher's rebuild handler (`dev-piece-watcher.ts`), and that watcher does not fire reliably on Windows for tool-made edits. A "normal restart" reuses the same PID (confirm with `netstat`/`Get-Process` bound to the dev port), so the server keeps serving the stale metadata. Find the PID bound to the dev API port and `Stop-Process -Id <pid> -Force`, then start fresh — every other change (prop text, logic inside `run()`) hot-reloads fine; only new action/trigger names or output-shape changes hit this.
- **`Property.Array`'s `properties` sub-schema never threads into its resolved `propsValue` type — confirmed in `packages/pieces/framework/src/lib/property/index.ts`.** `propsValue.someArrayProp` types as plain `unknown[]` regardless of what `properties` declares, so casting to the declared row type at the point of use is the only option; there is no framework-provided type-safe path around it. Document the cast in a comment so it doesn't read as an oversight on a later pass.
Expand Down
2 changes: 1 addition & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
services:
app:
image: ghcr.io/activepieces/activepieces:0.90.0
image: ghcr.io/activepieces/activepieces:0.90.1
container_name: activepieces-app
restart: unless-stopped
ports:
Expand All @@ -16,7 +16,7 @@ services:
networks:
- activepieces
worker:
image: ghcr.io/activepieces/activepieces:0.90.0
image: ghcr.io/activepieces/activepieces:0.90.1
restart: unless-stopped
depends_on:
- app
Expand Down
2 changes: 1 addition & 1 deletion docs/about/changelog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ name, brief, talk to, and reuse.
and adds one that sends, posts or files only when the sentence asks for it.
- **Edit with AI**: describe a change in the editor — *"only reply to paying customers"* —
and the instructions and tools are rewritten for you. Edits stay a draft until you press
**Save and go live**, and going live updates every flow already using the agent on its
**Publish**, and publishing updates every flow already using the agent on its
next run, with nothing to republish.

**Where an agent lives**
Expand Down
52 changes: 52 additions & 0 deletions docs/admin-guide/guides/ai-capabilities.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
title: "AI Capabilities"
icon: "bicep"
---

AI Capabilities let platform admins connect external services that give the Activepieces AI assistant additional abilities beyond what the AI model can do on its own.

Currently, you can configure capabilities for **Web Search, Web Scraping, and Image Generation**.

### **Why use AI Capabilities?**

AI Capabilities let your organization control which external services the AI assistant can use for specialized tasks.

For example, you can allow the assistant to search for current information on the web, extract content from webpages, or generate images when users need these capabilities.

Configuring these services at the platform level also means the required API credentials can be managed centrally rather than asking individual users to configure them.

### **Available AI Capabilities**

#### **Web Search**

Web Search lets the AI assistant search the live web for current information. 

This is useful for tasks where the assistant needs information that may not be available in the model itself.

#### **Web Scraping**

Web Scraping lets the AI assistant extract clean content from webpages as Markdown, including pages that are rendered with JavaScript.

This can be useful when the assistant needs to read and work with the full content of a webpage rather than just search results.

#### **Image Generation**

Image Generation lets the AI assistant create images such as realistic photos, marketing graphics with text, brand logos, and abstract art. The model is selected automatically for each request.

### **How To Set Up AI Capability**

The setup is similar across the available capabilities:

1. Go to **Platform Admin → AI Center → AI Capabilities**.
2. Find the capability you want to configure.
3. Click **Set up**.
4. Enter the API key for the external service.
5. Click **Save**.

<video src="https://cdn.activepieces.com/videos/docs/Set-up-ai-capabilities.mp4" controls />

### **AI Providers vs AI Capabilities**

**AI Providers** connect the AI models Activepieces uses, while **AI Capabilities** connect external services that give the AI assistant additional abilities such as web search, web scraping, and image generation.

<br />
44 changes: 44 additions & 0 deletions docs/admin-guide/guides/connections.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
title: "Connections"
icon: "chain"
---

Connections are the individual accounts of third-party apps or integrations connected to your Activepieces platform.

For example, when a user connects their Gmail account to Activepieces, that account is registered as one connection. If two users connect their individual Gmail accounts, Activepieces registers them as two separate connections, even though they both use the same Gmail piece.

**Example:**

Employee 1 Gmail **=** Connection 1

Employee 2 Gmail **=** Connection 2

![Connections Tab](https://cdn.activepieces.com/assets/connections-tab.png "Connections Tab")

The Connections page in Platform Admin gives administrators a detailed view of all app connections across every project on the platform. You can filter connections by **name, status, piece, project, and owner**.

For each connection, you can see:

- Its current status
- The piece or app it belongs to
- The project and owner associated with it
- When the connection was created
- The scope (whether it is available to specific projects or is a **Global Connection**)

If it is a Global Connection, you can also see the different projects that have access to that connection.

Learn more about [Connections vs Global Connections](https://app.mintlify.com/activepieces/activepieces/editor/ginika%2Fdraft-aug-31/~/766e454b-da15-4ea1-9e26-de1c537fc45b)

As more teams build automations, the number of connected accounts can grow quickly. The Connections page gives platform admins a single place to see which accounts are connected, who owns them, where they are used, and whether they are still active.

For example, if an employee leaves the company, an admin can use the **Owner** filter to identify connections associated with that user and understand which projects may depend on them.

Regularly reviewing connections can also help identify outdated ones 

### **Navigating Connections Via API**

Refer to [<u>API reference</u>](https://www.activepieces.com/docs/endpoints/connections/schema)

<br />

<br /><br />
14 changes: 14 additions & 0 deletions docs/admin-guide/guides/general.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
title: "General"
icon: "gears"
---

This is where you can change the basic appearance of your platform

You can change the platform name, icon, logo , fav icon url, primary color, or customize your theme colors

You also have the **Danger Zone,** which is where you can delete your platform.

**NB:** Once your platform is deleted, it cannot be recovered. Ensure you cancel your subscription before deleting your platform.

![General](https://cdn.activepieces.com/assets/general.png "General")
Loading
Loading