4fbd8b207deccdde9738dc8617a75eadbbd6fb84
4577 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4fbd8b207d |
chore: sync AI model catalog from models.dev (#20178)
Automated daily sync of `ai-providers.json` from [models.dev](https://models.dev). This PR updates pricing, context windows, and model availability based on the latest data. New models meeting inclusion criteria (tool calling, pricing data, context limits) are added automatically. Deprecated models are detected based on cost-efficiency within the same model family. **Please review before merging** — verify no critical models were incorrectly deprecated. Co-authored-by: FelixMalfait <6399865+FelixMalfait@users.noreply.github.com> |
||
|
|
f7d812fca6 |
fix: disable sync on seeded message and calendar channels (#20168)
## Summary The dev seeder creates `ConnectedAccount` records with fake OAuth tokens (`'exampleRefreshToken'` / `'exampleAccessToken'`) and points `MessageChannel` / `CalendarChannel` records at them with `isSyncEnabled: true`. When the sync cron jobs run in the demo workspace, they: 1. Pick up these seeded channels (filter is `isSyncEnabled: true` + pending sync stage) 2. Try to refresh the fake OAuth tokens 3. Mark the channels as `FAILED_INSUFFICIENT_PERMISSIONS` 4. Surface a "Sync lost with mailbox X — please reconnect" banner in the UI This banner appears every time the demo workspace is loaded, even though nothing is actually broken. ## Fix Set `isSyncEnabled: false` on all 12 seeded channels (6 message, 6 calendar). This is the canonical "don't sync this channel" mechanism — the same state a real user lands in when they toggle sync off in account settings. ## Why this approach - **ConnectedAccount records stay**: the demo workspace still shows Tim, Jony, Phil, Jane as having connected their email/calendar — realistic - **Pre-seeded messages and calendar events stay visible**: those don't depend on `isSyncEnabled` - **Crons no longer pick them up**: they filter on `isSyncEnabled: true`, so `false` short-circuits the entire sync attempt — no failure, no banner - **Semantically correct**: the seeded accounts have fake tokens that were never going to sync successfully; `isSyncEnabled: true` was effectively a lie - **No production code touched**: no `isDemo` flags, no magic-string detection, no workspace-ID filters in the cron path ## Alternatives considered and rejected - **Add an `isDemo` flag**: schema change, leaks demo knowledge into production tables - **Skip channels with fake tokens (`example*` pattern)**: hacky magic-string detection in the auth refresh path - **Filter demo workspace IDs in the cron**: production paths shouldn't reference demo IDs - **Don't activate demo workspaces**: breaks the demo workspace UX entirely ## Test plan - [ ] Reset the database and reseed (`npx nx database:reset twenty-server`) - [ ] Load the demo workspace — confirm no "Sync lost with mailbox" banner appears - [ ] Confirm seeded connected accounts still show in Settings → Accounts - [ ] Confirm pre-seeded messages and calendar events still appear in the UI - [ ] Confirm a real connected account (added via OAuth) still syncs normally — its channel will have `isSyncEnabled: true` and the cron will pick it up ## Related Companion to https://github.com/twentyhq/twenty/pull/20167, which removes the `--dev-mode` cron filter that was masking this banner issue in the dev image. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
c3a320c27b |
fix: register all cron jobs in twenty-app-dev image (#20167)
## Summary The `twenty-app-dev` Docker image previously passed `--dev-mode` to `cron:register:all`, which skipped all calendar, messaging, and workflow sync cron jobs (only 4 generic crons were registered). This caused periodic sync to silently stop after the initial import for community members using the dev image as their actual instance. ## What changed - Removed `--dev-mode` flag from `packages/twenty-docker/twenty-app-dev/rootfs/etc/s6-overlay/scripts/register-crons.sh` so the dev image registers all cron jobs (matching production behavior) - Removed the now-unused `--dev-mode` option, `DEV_MODE_COMMANDS` set, and conditional filtering logic from `cron-register-all.command.ts` ## Why this is safe - **No log noise**: cron jobs gracefully no-op when no connected accounts exist — they query for pending channels, find zero, and exit early - **No false banner**: the "reconnect account" banner only shows when a user explicitly connected an account whose OAuth later fails, which is correct behavior. No seed/demo data creates connected accounts, so a fresh dev instance won't see any banner - **Hiding crons just hid the symptom**: silently breaking sync with no user feedback is worse than showing the banner if OAuth is misconfigured ## Context Surfaced by a community member who reported that calendar sync cron jobs never appeared in the queue after restarting the dev image, and only the initial import worked. `--dev-mode` was added in #19138 as an optimization for development but it doesn't match how the dev image is actually used by community members deploying Twenty. ## Test plan - [ ] Build/run the `twenty-app-dev` image - [ ] Confirm worker logs show all cron jobs registering (calendar, messaging, workflow, etc.) - [ ] With no connected accounts: confirm no errors or log noise - [ ] With a connected Google calendar: confirm periodic sync triggers after ~5 minutes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
85752f8a61 | Bump 2.3.0 (#20169) | ||
|
|
8a0225e974 |
Dispatch root package.json hoisted deps and devDeps (#20140)
# Introduction Dispatching root package.json devDeps, prod deps Taking care of keeping non imported module used at build/ci level in the root package.json ## Motivation Avoid redundant deps declaration, better scoping allow better workspace deps granularity installation. <img width="385" height="247" alt="image" src="https://github.com/user-attachments/assets/9d7162ec-ba01-4f58-8563-38333733fdf0" /> --------- Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
636deffb93 |
i18n - translations (#20164)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
e1828b6f41 |
[AI] Add thread actions, filters, and archive support (#20068)
## PR Description ### Summary - Add AI chat thread actions: rename, archive (soft-delete via `deletedAt`), and hard-delete with confirmation. - Add chat thread filtering by status (active/archived/all), group-by mode, and last activity. - Rework drawer/side-panel thread lists to share thread sections, item menus, archive icons, and empty-state behavior. - Extend server chat thread model/API with `deletedAt`, mutations, broadcasts, and archive-aware stream guards. ### Decisions - Two-stage lifecycle: Archive sets `deletedAt` (soft); Delete is a separate action on archived threads that hard-deletes the row. Aligns with Twenty's soft-delete convention (Felix's suggestion). - `lastMessageAt` is derived from `MAX(agentMessage.createdAt)` on read, not stored. List query does inline aggregation for sort; `@ResolveField` covers single-thread / mutation paths so the schema contract is honest everywhere. Matches `timeline-messaging.service.ts` precedent and the existing `totalInputCredits` / `totalOutputCredits` `@ResolveField` pattern in the same resolver. - Replaced auto-CRUD `chatThreads` (cursor-paginated Connection) with a custom `[AgentChatThreadDTO!]` resolver. Frontend metadata-store treats threads as a flat collection and filters/sorts client-side, so cursor pagination was performative. - Sending in an archived chat unarchives it optimistically on the client and authoritatively on the server. - Grouping and last-activity filtering use `lastMessageAt ?? updatedAt` so archive/rename don't bump threads in the list. - Kept metadata-store core API unchanged; AI chat uses the same local cast pattern already used by other metadata-store partial updates. https://github.com/user-attachments/assets/1b179b7b-1a2a-4a7a-aa0a-c88f6f051a87 |
||
|
|
4b76457217 |
Select application excluding logo (#20159)
## Context This is a temporary fix for cross-version upgrade process, a better fix would be to expose an hasInstanceCommandBeenRun() util (and later a decorator) |
||
|
|
b44fb1ad23 |
fix(security): reject ?token= URL query parameter for authentication (#20154)
## Summary
Removes the `?token=` URL query-parameter fallback from JWT
authentication. Every authenticated route (`/graphql`, `/metadata`,
REST, etc.) used to accept a full workspace JWT in the URL alongside the
`Authorization` header. The fallback was intended for the REST API
Playground only, but it was wired into the global Passport JWT extractor
and applied to every route.
URL-borne tokens leak into:
- Server access logs (nginx / Apache / CDN / proxy / load balancer)
- Log aggregators (Datadog, CloudWatch, Loki, Sumo, …)
- Browser history (and synced across devices)
- `Referer` headers when navigating to external pages
- Browser extensions with `tabs`/`webNavigation` permissions
A leaked log line was equivalent to a leaked workspace credential for
the lifetime of the token.
## What changed
- **`jwt-wrapper.service.ts`** — `extractJwtFromRequest()` is now
header-only (`ExtractJwt.fromAuthHeaderAsBearerToken()`). No URL
fallback anywhere in the system.
- **`open-api.service.ts` / `base-schema.utils.ts`** — Dropped the
`token?: string` plumbing that propagated the URL token into the schema
description. The "Authentication" section gains a "Never put your token
in a URL" warning. The "Usage with LLMs" section is rewritten to point
at the **Twenty MCP server** (header-authenticated, exposes typed tools
— the right tool for AI agents) instead of telling users to paste
tokenized OpenAPI URLs into Cursor/ChatGPT.
- **`RestPlayground.tsx`** — Now fetches the OpenAPI schema with
`Authorization: Bearer ${playgroundApiKey}` and passes the JSON document
to Scalar via `spec.content` instead of constructing a URL with
`?token=`. Aborts in-flight fetches on unmount/key change.
- **New integration test** — Asserts `?token=` is rejected on `/rest/*`,
`/graphql`, `/metadata`, and that `/rest/open-api/core?token=` returns
the unauthenticated base schema (no workspace object paths).
## Why not keep `?token=` scoped to the OpenAPI endpoint only
The first instinct was to narrow the fallback to just
`/rest/open-api/*`, since that endpoint is what the Scalar playground
component fetches. But the same log-leakage attack still applies to that
endpoint — the workspace JWT would still sit in access logs, just from
one URL pattern instead of all of them. The cleaner long-term fix is to
remove the URL pattern entirely and let the playground fetch with a
header (Scalar supports `spec.content` natively). For LLM agent use, the
MCP server is a strictly better path — typed tools, OAuth or
header-based API key auth, no tokens in URLs anywhere.
## Not affected
File downloads at `file-url.service.ts` also use `?token=` URLs but with
separate, short-lived `FILE`-typed tokens validated by
`file-by-id.guard.ts` directly (not via `extractJwtFromRequest`). That
mechanism is scoped per-file with limited TTL and is acceptable.
## Action required for users
Anyone who previously pasted `?token=` URLs into LLM tools, scripts,
bookmarks, or shared configs should rotate their workspace API keys.
Those tokens are likely captured in server logs / chat histories
somewhere.
## Test plan
- [x] `npx nx typecheck twenty-server` — clean
- [x] `npx nx typecheck twenty-front` — clean
- [x] `npx nx lint:diff-with-main twenty-server` — clean
- [x] `npx nx lint:diff-with-main twenty-front` — clean
- [x] OpenAPI utils unit tests + snapshots — 11/11 pass
- [ ] Run the new integration test against a live server: `nx run
twenty-server:test:integration:with-db-reset` and verify
`url-token-auth-rejection.integration-spec.ts` passes
- [ ] Manually open Settings → Playground → REST, confirm the schema
loads (now via Bearer header instead of `?token=` URL)
- [ ] Manually verify `POST /metadata?token=<jwt>` (no Authorization
header) returns Forbidden, and the same request with the token in the
header returns the user
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
|
||
|
|
83db37d33f |
chore(deps): bump @sentry/profiling-node from 10.27.0 to 10.51.0 (#20149)
Bumps [@sentry/profiling-node](https://github.com/getsentry/sentry-javascript) from 10.27.0 to 10.51.0. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/getsentry/sentry-javascript/releases"><code>@sentry/profiling-node</code>'s releases</a>.</em></p> <blockquote> <h2>10.51.0</h2> <h3>Important Changes</h3> <ul> <li> <p><strong>feat(cloudflare): Add trace propagation for RPC method calls (<a href="https://redirect.github.com/getsentry/sentry-javascript/pull/20343">#20343</a>)</strong></p> <p>Trace context is now propagated across Cloudflare Workers RPC calls, connecting traces between Workers and Durable Objects. This feature is opt-in and requires setting <code>enableRpcTracePropagation: true</code> in your SDK configuration:</p> <pre lang="ts"><code>// Worker export default Sentry.withSentry( env => ({ dsn: env.SENTRY_DSN, enableRpcTracePropagation: true, }), handler, ); <p>// Durable Object<br /> export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(<br /> env => ({<br /> dsn: env.SENTRY_DSN,<br /> enableRpcTracePropagation: true,<br /> }),<br /> MyDurableObjectBase,<br /> );<br /> </code></pre></p> </li> <li> <p><strong>feat(hono)!: Change setup for <code>@sentry/hono/node</code> (<code>init</code> in external file) (<a href="https://redirect.github.com/getsentry/sentry-javascript/pull/20497">#20497</a>)</strong></p> <p>To improve Node.js instrumentation, the <code>sentry()</code> middleware exported from <code>@sentry/hono/node</code> no longer accepts configuration options. Instead, you must configure the SDK by calling <code>Sentry.init()</code> in a dedicated instrumentation file that runs before your application code (read more in the <a href="https://github.com/getsentry/sentry-javascript/blob/develop/packages/hono/README.md">Hono SDK readme</a>:</p> <pre lang="ts"><code>// instrument.mjs (or instrument.ts) import * as Sentry from '@sentry/hono/node'; <p>Sentry.init({<br /> dsn: '<strong>DSN</strong>',<br /> tracesSampleRate: 1.0,<br /> });<br /> </code></pre></p> </li> <li> <p><strong>feat(nitro): Add <code>@sentry/nitro</code> SDK (<a href="https://redirect.github.com/getsentry/sentry-javascript/pull/19224">#19224</a>)</strong></p> <p>A new <code>@sentry/nitro</code> package provides first-class Sentry support for <a href="https://nitro.build/">Nitro</a> applications, with HTTP handler and error instrumentation, middleware tracing, request isolation, and build-time source map uploading via <code>withSentryConfig</code>. Read more in the <a href="https://docs.sentry.io/platforms/javascript/guides/nitro/">Nitro SDK docs</a> and the <a href="https://github.com/getsentry/sentry-javascript/blob/develop/packages/nitro/README.md">Nitro SDK readme</a>.</p> </li> </ul> <h3>Other Changes</h3> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md"><code>@sentry/profiling-node</code>'s changelog</a>.</em></p> <blockquote> <h2>10.51.0</h2> <h3>Important Changes</h3> <ul> <li> <p><strong>feat(cloudflare): Add trace propagation for RPC method calls (<a href="https://redirect.github.com/getsentry/sentry-javascript/pull/20343">#20343</a>)</strong></p> <p>Trace context is now propagated across Cloudflare Workers RPC calls, connecting traces between Workers and Durable Objects. This feature is opt-in and requires setting <code>enableRpcTracePropagation: true</code> in your SDK configuration:</p> <pre lang="ts"><code>// Worker export default Sentry.withSentry( env => ({ dsn: env.SENTRY_DSN, enableRpcTracePropagation: true, }), handler, ); <p>// Durable Object<br /> export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(<br /> env => ({<br /> dsn: env.SENTRY_DSN,<br /> enableRpcTracePropagation: true,<br /> }),<br /> MyDurableObjectBase,<br /> );<br /> </code></pre></p> </li> <li> <p><strong>feat(hono)!: Change setup for <code>@sentry/hono/node</code> (<code>init</code> in external file) (<a href="https://redirect.github.com/getsentry/sentry-javascript/pull/20497">#20497</a>)</strong></p> <p>To improve Node.js instrumentation, the <code>sentry()</code> middleware exported from <code>@sentry/hono/node</code> no longer accepts configuration options. Instead, you must configure the SDK by calling <code>Sentry.init()</code> in a dedicated instrumentation file that runs before your application code (read more in the <a href="https://github.com/getsentry/sentry-javascript/blob/develop/packages/hono/README.md">Hono SDK readme</a>:</p> <pre lang="ts"><code>// instrument.mjs (or instrument.ts) import * as Sentry from '@sentry/hono/node'; <p>Sentry.init({<br /> dsn: '<strong>DSN</strong>',<br /> tracesSampleRate: 1.0,<br /> });<br /> </code></pre></p> </li> <li> <p><strong>feat(nitro): Add <code>@sentry/nitro</code> SDK (<a href="https://redirect.github.com/getsentry/sentry-javascript/pull/19224">#19224</a>)</strong></p> <p>A new <code>@sentry/nitro</code> package provides first-class Sentry support for <a href="https://nitro.build/">Nitro</a> applications, with HTTP handler and error instrumentation, middleware tracing, request isolation, and build-time source map uploading via <code>withSentryConfig</code>. Read more in the <a href="https://docs.sentry.io/platforms/javascript/guides/nitro/">Nitro SDK docs</a> and the <a href="https://github.com/getsentry/sentry-javascript/blob/develop/packages/nitro/README.md">Nitro SDK readme</a>.</p> </li> </ul> <h3>Other Changes</h3> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/getsentry/sentry-javascript/commit/dc0b839ff4896cf90a02f5c1a6de54a31302dcf3"><code>dc0b839</code></a> release: 10.51.0</li> <li><a href="https://github.com/getsentry/sentry-javascript/commit/b3cabee9a9348b9e67332262d44d3d1900424199"><code>b3cabee</code></a> Merge pull request <a href="https://redirect.github.com/getsentry/sentry-javascript/issues/20599">#20599</a> from getsentry/prepare-release/10.51.0</li> <li><a href="https://github.com/getsentry/sentry-javascript/commit/3be99a9afa77e49578e6839e4b32f97fb04fb0f8"><code>3be99a9</code></a> meta(changelog): Update changelog for 10.51.0</li> <li><a href="https://github.com/getsentry/sentry-javascript/commit/bea1aad42277db894d5a299bfec3cdd633d6baf0"><code>bea1aad</code></a> test(browser): Unflake some more tests (<a href="https://redirect.github.com/getsentry/sentry-javascript/issues/20591">#20591</a>)</li> <li><a href="https://github.com/getsentry/sentry-javascript/commit/50aa0859b3a188d34d0317dab3ad57f2140f02fe"><code>50aa085</code></a> test(node): Unflake postgres tests (<a href="https://redirect.github.com/getsentry/sentry-javascript/issues/20593">#20593</a>)</li> <li><a href="https://github.com/getsentry/sentry-javascript/commit/1166839112c4766f210124dc0486ebbfd6db104b"><code>1166839</code></a> fix(hono): Distinguish <code>.use()</code> middleware in sub-apps from <code>.all()</code> handlers...</li> <li><a href="https://github.com/getsentry/sentry-javascript/commit/217ad4a69554281806eccbfeac1b27c4f43f6ffa"><code>217ad4a</code></a> test(node): Fix flaky ANR test (<a href="https://redirect.github.com/getsentry/sentry-javascript/issues/20592">#20592</a>)</li> <li><a href="https://github.com/getsentry/sentry-javascript/commit/91ffb3fac90835ab160f8152527a54a5d64f3250"><code>91ffb3f</code></a> test(node): Fix flaky worker thread integration test (<a href="https://redirect.github.com/getsentry/sentry-javascript/issues/20588">#20588</a>)</li> <li><a href="https://github.com/getsentry/sentry-javascript/commit/c4e3902c9297147158e730f017aba96e83ef619e"><code>c4e3902</code></a> chore(ci): Do not report flaky test issues if we cannot find a test name (<a href="https://redirect.github.com/getsentry/sentry-javascript/issues/20">#20</a>...</li> <li><a href="https://github.com/getsentry/sentry-javascript/commit/c0005cd387f3a7ea6fbb2e85041562c7f32e0484"><code>c0005cd</code></a> test(node): Update timeout for cron integration tests (<a href="https://redirect.github.com/getsentry/sentry-javascript/issues/20586">#20586</a>)</li> <li>Additional commits viewable in <a href="https://github.com/getsentry/sentry-javascript/compare/10.27.0...10.51.0">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
5bdbfe651e |
chore(deps): bump postal-mime from 2.6.1 to 2.7.4 (#20150)
Bumps [postal-mime](https://github.com/postalsys/postal-mime) from 2.6.1 to 2.7.4. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/postalsys/postal-mime/releases">postal-mime's releases</a>.</em></p> <blockquote> <h2>v2.7.4</h2> <h2><a href="https://github.com/postalsys/postal-mime/compare/v2.7.3...v2.7.4">2.7.4</a> (2026-03-17)</h2> <h3>Bug Fixes</h3> <ul> <li>add missing originalKey to Header type and Uint8Array to Attachment content (<a href="https://github.com/postalsys/postal-mime/commit/92cc91c1c8477e0462cb0e93ddf8ea6aec6534d0">92cc91c</a>)</li> <li>include originalKey in parsed headers output (<a href="https://github.com/postalsys/postal-mime/commit/83521c87f62e5e095ae09913c70798f20e2ab347">83521c8</a>)</li> <li>preserve __esModule and .default in CJS build for bundler interop (<a href="https://github.com/postalsys/postal-mime/commit/1466910e31608b9e5307724ecc6a0a3a70556048">1466910</a>)</li> <li>prevent RFC 2047 encoded-word address fabrication (<a href="https://github.com/postalsys/postal-mime/commit/844f92023d49d819ef13b9ad5c50b7c346eb02d3">844f920</a>)</li> </ul> <h2>v2.7.3</h2> <h2><a href="https://github.com/postalsys/postal-mime/compare/v2.7.2...v2.7.3">2.7.3</a> (2026-01-09)</h2> <h3>Bug Fixes</h3> <ul> <li>correct TypeScript type definitions to match implementation (<a href="https://github.com/postalsys/postal-mime/commit/b225d7cca422cb9bc3ab5301e94c4c0bef9a69e2">b225d7c</a>)</li> </ul> <h2>v2.7.2</h2> <h2><a href="https://github.com/postalsys/postal-mime/compare/v2.7.1...v2.7.2">2.7.2</a> (2026-01-08)</h2> <h3>Bug Fixes</h3> <ul> <li>add null checks for contentType.parsed access (<a href="https://github.com/postalsys/postal-mime/commit/ad8f4c62e0972fd0244859ee5a5184b2cac26395">ad8f4c6</a>)</li> <li>improve RFC compliance for MIME parsing (<a href="https://github.com/postalsys/postal-mime/commit/e004c3acb29d72ed7eaf1b0b66351cf8b82b970d">e004c3a</a>)</li> </ul> <h2>v2.7.1</h2> <h2><a href="https://github.com/postalsys/postal-mime/compare/v2.7.0...v2.7.1">2.7.1</a> (2025-12-22)</h2> <h3>Bug Fixes</h3> <ul> <li>Add null checks for contentDisposition.parsed access (<a href="https://github.com/postalsys/postal-mime/commit/fd54c37093cc64737c6bb17986bc9d052d2d5add">fd54c37</a>)</li> </ul> <h2>v2.7.0</h2> <h2><a href="https://github.com/postalsys/postal-mime/compare/v2.6.1...v2.7.0">2.7.0</a> (2025-12-22)</h2> <h3>Features</h3> <ul> <li>add headerLines property exposing raw header lines (<a href="https://github.com/postalsys/postal-mime/commit/c79a02ab05d9cac44e05e95a433752ff292aa5eb">c79a02a</a>)</li> </ul> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/postalsys/postal-mime/blob/master/CHANGELOG.md">postal-mime's changelog</a>.</em></p> <blockquote> <h2><a href="https://github.com/postalsys/postal-mime/compare/v2.7.3...v2.7.4">2.7.4</a> (2026-03-17)</h2> <h3>Bug Fixes</h3> <ul> <li>add missing originalKey to Header type and Uint8Array to Attachment content (<a href="https://github.com/postalsys/postal-mime/commit/92cc91c1c8477e0462cb0e93ddf8ea6aec6534d0">92cc91c</a>)</li> <li>include originalKey in parsed headers output (<a href="https://github.com/postalsys/postal-mime/commit/83521c87f62e5e095ae09913c70798f20e2ab347">83521c8</a>)</li> <li>preserve __esModule and .default in CJS build for bundler interop (<a href="https://github.com/postalsys/postal-mime/commit/1466910e31608b9e5307724ecc6a0a3a70556048">1466910</a>)</li> <li>prevent RFC 2047 encoded-word address fabrication (<a href="https://github.com/postalsys/postal-mime/commit/844f92023d49d819ef13b9ad5c50b7c346eb02d3">844f920</a>)</li> </ul> <h2><a href="https://github.com/postalsys/postal-mime/compare/v2.7.2...v2.7.3">2.7.3</a> (2026-01-09)</h2> <h3>Bug Fixes</h3> <ul> <li>correct TypeScript type definitions to match implementation (<a href="https://github.com/postalsys/postal-mime/commit/b225d7cca422cb9bc3ab5301e94c4c0bef9a69e2">b225d7c</a>)</li> </ul> <h2><a href="https://github.com/postalsys/postal-mime/compare/v2.7.1...v2.7.2">2.7.2</a> (2026-01-08)</h2> <h3>Bug Fixes</h3> <ul> <li>add null checks for contentType.parsed access (<a href="https://github.com/postalsys/postal-mime/commit/ad8f4c62e0972fd0244859ee5a5184b2cac26395">ad8f4c6</a>)</li> <li>improve RFC compliance for MIME parsing (<a href="https://github.com/postalsys/postal-mime/commit/e004c3acb29d72ed7eaf1b0b66351cf8b82b970d">e004c3a</a>)</li> </ul> <h2><a href="https://github.com/postalsys/postal-mime/compare/v2.7.0...v2.7.1">2.7.1</a> (2025-12-22)</h2> <h3>Bug Fixes</h3> <ul> <li>Add null checks for contentDisposition.parsed access (<a href="https://github.com/postalsys/postal-mime/commit/fd54c37093cc64737c6bb17986bc9d052d2d5add">fd54c37</a>)</li> </ul> <h2><a href="https://github.com/postalsys/postal-mime/compare/v2.6.1...v2.7.0">2.7.0</a> (2025-12-22)</h2> <h3>Features</h3> <ul> <li>add headerLines property exposing raw header lines (<a href="https://github.com/postalsys/postal-mime/commit/c79a02ab05d9cac44e05e95a433752ff292aa5eb">c79a02a</a>)</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/postalsys/postal-mime/commit/178f1ef0b1cd0047e1b8e690beabfec541b4daa7"><code>178f1ef</code></a> chore(master): release 2.7.4 (<a href="https://redirect.github.com/postalsys/postal-mime/issues/88">#88</a>)</li> <li><a href="https://github.com/postalsys/postal-mime/commit/1f7ba618d42d34b779157dfa33794cbae383a24d"><code>1f7ba61</code></a> chore: bump devDependencies</li> <li><a href="https://github.com/postalsys/postal-mime/commit/83521c87f62e5e095ae09913c70798f20e2ab347"><code>83521c8</code></a> fix: include originalKey in parsed headers output</li> <li><a href="https://github.com/postalsys/postal-mime/commit/b0d7b11550a2a3c65a52a2adf4f8281058023cab"><code>b0d7b11</code></a> test: improve test coverage across codebase</li> <li><a href="https://github.com/postalsys/postal-mime/commit/ebc5ce619649d13ad72f4d12414f3e337a9e248c"><code>ebc5ce6</code></a> refactor: simplify and clean up codebase</li> <li><a href="https://github.com/postalsys/postal-mime/commit/1466910e31608b9e5307724ecc6a0a3a70556048"><code>1466910</code></a> fix: preserve __esModule and .default in CJS build for bundler interop</li> <li><a href="https://github.com/postalsys/postal-mime/commit/844f92023d49d819ef13b9ad5c50b7c346eb02d3"><code>844f920</code></a> fix: prevent RFC 2047 encoded-word address fabrication</li> <li><a href="https://github.com/postalsys/postal-mime/commit/24dc6c64dfb43d89a8c8837ec941c96ebfa2c1fa"><code>24dc6c6</code></a> test: update type check test with originalKey property</li> <li><a href="https://github.com/postalsys/postal-mime/commit/92cc91c1c8477e0462cb0e93ddf8ea6aec6534d0"><code>92cc91c</code></a> fix: add missing originalKey to Header type and Uint8Array to Attachment content</li> <li><a href="https://github.com/postalsys/postal-mime/commit/aa5baeafa6ffd093ab447c22d20e5da25051faff"><code>aa5baea</code></a> docs: add link to full documentation site</li> <li>Additional commits viewable in <a href="https://github.com/postalsys/postal-mime/compare/v2.6.1...v2.7.4">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
bddd23fd9c |
Fix application icons (#20142)
fixes application chip (icon Name) in all setting tables ## After <img width="1200" height="896" alt="image" src="https://github.com/user-attachments/assets/bd377f47-1d52-4142-b904-f2ce90c1db78" /> <img width="1200" height="917" alt="image" src="https://github.com/user-attachments/assets/f49cc742-f11e-47e3-86ed-34beffe493c7" /> <img width="1234" height="878" alt="image" src="https://github.com/user-attachments/assets/2ab459de-5f9d-4d39-9490-eec4ed9ee432" /> <img width="1239" height="845" alt="image" src="https://github.com/user-attachments/assets/3c1bf258-285a-47b9-a60d-05ba1564334d" /> <img width="1183" height="907" alt="image" src="https://github.com/user-attachments/assets/715b2470-2d88-48e3-88ac-d3daf3451717" /> <img width="1300" height="912" alt="image" src="https://github.com/user-attachments/assets/d7c829fa-bf1d-4f19-82de-a8bf29e22bfa" /> |
||
|
|
842e679cc6 |
fix(billing): gate AI credit-cap at entry points instead of workflow executor (#20096)
## Background The 2026-04-26 incident saw 716M Sonnet 4.6 tokens consumed in a single trial workspace. Two causes: failed agent executions weren't billed (addressed by #20065) and the credit-cap gate had been removed from `WorkflowExecutorWorkspaceService.executeStep` in #19904, leaving no enforcement point at all. ## Why not just revert #19904 #19904 was right that gating at the workflow executor is too coarse. When one user exhausted a workspace's credits via chat, *all* workflows hard-failed mid-run — including cheap DB/CRUD/branch automations costing essentially nothing. Reverting would re-introduce that cliff. ## New design: gate at the AI entry points The chat resolver already gates this way (`agent-chat.resolver.ts:137-148`). This PR replicates the same pattern at every other point where the workspace can incur real AI cost: - `executeAgent` in `agent-async-executor.service.ts` - the REST handler in `ai-generate-text.controller.ts` - `generateThreadTitle` in `agent-title-generation.service.ts` In each, after auth/validation: skip if `IS_BILLING_ENABLED` is false; otherwise call `BillingService.canBillMeteredProduct(workspaceId, BillingProductKey.WORKFLOW_NODE_EXECUTION)`; on `false`, throw `BillingException(BILLING_CREDITS_EXHAUSTED)`. No new method, no new exception code, no new product key. This matches industry convention (Lovable/Replit also gate at the expensive-operation boundary, not at every cheap step). ## Deliberately not gated - `WorkflowExecutorWorkspaceService.executeStep` — the design choice is now intentional, so the #19904 TODO is replaced by a one-line absolute-behavior comment explaining why the gate isn't here. Cheap workflow steps (DB CRUD, branching, action steps) are not gated, so a chat-driven cap exhaustion does not block non-AI automations. - `repair-tool-call.util` — repair is a sub-call inside an already-gated AI flow. If the parent is gated, repair will naturally not run. Adding a gate here adds complexity without value. ## Net effect A workspace that exhausts credits via chat or AI agent stops making AI calls. Its non-AI workflows continue running normally. A workflow with both AI and non-AI steps fails at the AI step with `BILLING_CREDITS_EXHAUSTED`, but downstream non-AI steps that don't depend on the AI output still run. ## Conflicts This PR overlaps with three other in-flight PRs in the same files. None of them touch the gate logic; rebasing on top of any of them is trivial: - #20065 (agent-async-executor): adds `workspaceId` to `executeAgent` args and bills in `finally`. The gate at the top of `executeAgent` from this PR sits naturally above that. - #20066 (REST controller): adds usage billing to the controller. - #20067 (title gen): adds usage billing to title generation and tool-call repair. Recommend landing #20065/#20066/#20067 first; this PR rebases trivially on top. ## Tests Out of scope per the PR series convention. The existing chat-resolver gate isn't unit-tested either; this PR follows the same precedent. Follow-up: add integration coverage that exercises a workspace at `hasReachedCurrentPeriodCap=true` against each of the three new gates plus the pre-existing chat-resolver gate. ## Future follow-ups - Per-user soft cap inside a workspace (the Lovable Business-tier pattern), so one user can't exhaust the workspace's cap. - Pre-flight cost estimate so the user sees an "approaching cap" warning before the hard stop. - Rename `BillingProductKey.WORKFLOW_NODE_EXECUTION` — the name predates this design choice and is misleading now that it gates AI entry points rather than workflow nodes. ## Test plan - [ ] Trigger a workspace into `hasReachedCurrentPeriodCap=true`. - [ ] Send a chat message — expect failure with `BILLING_CREDITS_EXHAUSTED`. - [ ] Run a workflow whose only AI step is an `ai-agent` action — expect that step to fail with `BILLING_CREDITS_EXHAUSTED`, downstream non-AI steps still run. - [ ] POST to `/rest/ai/generate-text` — expect `BILLING_CREDITS_EXHAUSTED`. - [ ] Create a new chat thread (which kicks off `generateThreadTitle`) — expect `BILLING_CREDITS_EXHAUSTED`. - [ ] Run a workflow with no AI step (only DB CRUD/branching/actions) — expect it to run unaffected. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
a713f8d87f |
CalDAV: support Digest auth (#20135)
Adds digest auth support for CalDAV, mostly used by legacy servers /closes https://github.com/twentyhq/twenty/issues/19922 |
||
|
|
fd6d5f895d |
Ai Chat - Caching optim (#20126)
EDIT :
- solving auto-caching from Anthropic by updating ai-sdk/anthropic +
adding providerOption at stream level
- concerning Bedrock, it needs breakpoint
**1. Breakpoints were only on the system prompt**
The code already placed a cache marker on the system prompt (~10K
tokens). But the conversation history — which can grow to hundreds of
thousands of tokens — had no marker, so Anthropic re-read it at full
price on every turn.
The fix adds a prepareStep hook inside streamText that stamps the last
message with a cache breakpoint before every LLM call. Anthropic then
caches the entire conversation prefix, and subsequent turns read it at
$0.30/M instead of $3/M.
prepareStep is used rather than a one-shot pre-processing step because
an agentic turn makes multiple internal LLM calls as tool results
accumulate — the hook refreshes the breakpoint before each one.
**2. Bedrock was using the wrong field**
The system prompt marker for Bedrock was set as cacheControl: { type:
'ephemeral' } — which is the Anthropic wire format. The Bedrock Converse
API expects cachePoint: { type: 'default' }. The system prompt was
silently not being cached on Bedrock at all.
Both the system prompt and the new prepareStep now go through a shared
getCacheProviderOptions helper that returns the correct field per
provider.
**3. Persisted cached token usage to monitor cache strat. efficiency**
|
||
|
|
11628d19a3 |
add recurring calendar events for google cal (#19748)
Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
abfa6200dd |
ssrf hardening (#19963)
Hardened CalDav with new approach of wrapping axios ssrf http agent to fetch via `@lifeomic/axios-fetch` because `tsdav` only accept `fetch` override. Also Hardened test endpoint |
||
|
|
480e5796ec |
fix(ai): render record links inside markdown headings in AI chat (#20074)
## Summary Adds `h1`–`h6` component overrides to `LazyMarkdownRenderer` so that `[[record:...]]` references placed inside markdown headings in the AI chat are parsed by `processChildrenForRecordLinks` and rendered as clickable `RecordLink` chips, matching the behavior already in place for `p`, `li`, `td`, `th`, and `a`. Fixes #20072 ## Test plan - [ ] In AI chat, ask a question whose answer places a record reference inside a markdown heading (e.g. `## Found [[person:uuid:John Doe]]`) and confirm a clickable `RecordLink` chip renders instead of the raw `[[...]]` text. - [ ] Verify heading styling (sizes, weights, margins) is unchanged. Generated with [Claude Code](https://claude.ai/code) --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: nitin <ehconitin@users.noreply.github.com> |
||
|
|
b49e58dfc6 |
Fix click house migration (#20127)
`it does not support renaming of multiple tables in single query.` @etiennejouan manually fix the corrupted clickhouse instance |
||
|
|
a6118b7dc3 |
i18n - translations (#20125)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
19ee9444ed | add UpsertViewWidget resolver (#20053) | ||
|
|
c476c6c80b |
chore: sync AI model catalog from models.dev (#20122)
Automated daily sync of `ai-providers.json` from [models.dev](https://models.dev). This PR updates pricing, context windows, and model availability based on the latest data. New models meeting inclusion criteria (tool calling, pricing data, context limits) are added automatically. Deprecated models are detected based on cost-efficiency within the same model family. **Please review before merging** — verify no critical models were incorrectly deprecated. Co-authored-by: FelixMalfait <6399865+FelixMalfait@users.noreply.github.com> |
||
|
|
d2dda67596 |
Fix upgrade --start-from-workspace-id (#20116)
# Introduction Prevent using both `--start-from-workspace-id` and `--workspace` When any of the two are being passed we prevent passing to the next instance segment, it would require an upgrade re run even if legit When `--start-from-workspace-id` is passed we filter from all the fetched active or suspended workspace ids and apply equivalent filter as before |
||
|
|
7ea1dfdd49 |
i18n - translations (#20115)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
3290bf3ab1 |
fix(rest-api): prevent silent pagination failures and include valid options in enum validation errors (#20092)
# Summary (fixes #20044) This PR implements two fixes for the REST API to enforce stricter validation and provide better error messages. Issue 1: Cursor parameter silently ignored Problem: When users provided common cursor aliases (e.g., cursor, after, before) instead of the correct parameter names (starting_after, ending_before), the API silently ignored them and returned page 1 on every request. Solution: Added strict validation to detect common cursor aliases and throw a clear error directing users to use the correct parameter names. Files modified: - packages/twenty-server/src/engine/api/rest/input-request-parsers/rest-input-request-parser.exception.ts - packages/twenty-server/src/engine/api/rest/input-request-parsers/starting-after-parser-utils/parse-starting-after-rest-request.util.ts - packages/twenty-server/src/engine/api/rest/input-request-parsers/ending-before-parser-utils/parse-ending-before-rest-request.util.ts - Test files for both parsers Example error: Invalid cursor parameter 'cursor'. Use 'starting_after' for pagination. --- Issue 2: OpportunityStageEnum not validated on REST Problem: When creating or updating opportunities via REST with an invalid stage value, the API either silently dropped the value or returned a generic error without listing valid options. Solution: Updated the SELECT field validation to include valid options in the error message. Files modified: - packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-rating-and-select-field-or-throw.util.ts - Test file Example error: Invalid value "BAD_VALUE" for field "stage". Valid values are: NEW, SCREENING, MEETING, PROPOSAL, CUSTOMER --- ### Testing - Added 5 new test cases for `parse-starting-after-rest-request.util.ts` - Added 6 new test cases for `parse-ending-before-rest-request.util.ts` - Added 1 new test case for `validate-rating-and-select-field-or-throw.util.ts` - All 21 tests passing --- Breaking Changes None - correct usage is unaffected. --------- Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com> |
||
|
|
fbaea0639a |
Billing - optimize usageEvent CH table (#20019)
- Update usageEvent clickhouse table, partitioning, indexing and projection (auto materialized view) to optimize credit usage queries - Add caching for available credits and billing subscription To do in next PR: deprecate enforceCapUsage cron. Bonus : real-time on billingSubscription |
||
|
|
8998009805 |
Stop reseting isListed and is featured after each sync (#20111)
isListed and isFeatured are manually updated by the admin, it should not be updated if the application already exists |
||
|
|
a5cd64daf5 |
refactor: standardize JsonStringified casing (#20101)
## Summary - Rename safeParseRelativeDateFilterJSONStringified to safeParseRelativeDateFilterJsonStringified - Update the matching utility file, exports, tests, and workflow usages Part of #19839. ## Validation - CI passed |
||
|
|
df3e217d64 |
fix(ai-billing): bill executeAgent in a finally block so failed runs don't leak (#20065)
## Summary
`AgentAsyncExecutorService.executeAgent` consumes Anthropic tokens at
two points (the main `generateText` and the optional structured-output
sub-call). Billing was previously the **caller's** responsibility,
executed only after `executeAgent` returned. If `executeAgent` threw —
e.g. when `structuredResult.output == null` for a schema-mismatched
response, or anything caught by the catch-and-rethrow — we paid
Anthropic but never recorded a `usageEvent`. Likely the dominant source
of the 716M-token-vs-3.27-credits discrepancy seen on the affected
workspace in the 2026-04-26 incident.
## What changed
- Inject `AiBillingService` into `AgentAsyncExecutorService`. Add
`workspaceId` (required), `userWorkspaceId`, and `operationType`
(default `AI_WORKFLOW_TOKEN`) to `executeAgent`'s args.
- Capture `accumulatedUsage`, `cacheCreationTokens`, and
`nativeWebSearchCallCount` into mutable locals as each `generateText`
resolves. A throw between the main and structured-output calls still
bills the first call's tokens; the schema-validation throw still bills
the merged usage.
- Wrap the body in `try { ... } finally { ... }`. The finally calls
`calculateAndBillUsage` and `billNativeWebSearchUsage`, each guarded by
its own `try/catch + logger.error` so a billing exception can't mask the
original execution error or block the second emit.
- `ai-agent.workflow-action.ts`: pass the new args; drop the
now-redundant billing calls and `AiBillingService` injection.
`AiBillingModule` removed from this action's module imports.
- `run-evaluation-input.job.ts`: pass `workspaceId` (already in `data`)
and `userWorkspaceId: null`. **As a side effect, the eval pipeline now
bills correctly** — closing an additional billing leak from the audit
(`RunEvaluationInputJob` previously called `executeAgent` and discarded
`executionResult.usage`).
## Behavior change worth calling out
Previously, failed agent executions were silently free. They will now be
billed for the tokens Anthropic charged us. This is intentional and
correct.
## Test plan
- [ ] Trigger a workflow agent action that succeeds — `usageEvent` count
should match what was previously emitted.
- [ ] Trigger a workflow agent with a JSON response schema and ambiguous
input that produces a non-schema-conforming output
(`structuredResult.output == null`) — verify a `usageEvent` row is now
written for the consumed tokens (was 0 rows previously).
- [ ] Trigger a `runEvaluationInput` GraphQL mutation — verify a
`usageEvent` row is written (was 0 rows previously).
## Notes for review
- Conflicts trivially with the Sentry-context PR on
`run-evaluation-input.job.ts`. Recommend merging Sentry-context first;
this PR's 2-line argument addition rebases inside that PR's
`aiCallContextService.run(...)` callback wrapper.
- A small follow-up after this lands: thread `billingContext` through
the `experimental_repairToolCall` callback in
`agent-async-executor.service.ts:198` (currently marked with a TODO from
the title-gen+repair-tool PR).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
1c06506256 |
chore: sync AI model catalog from models.dev (#20106)
Automated daily sync of `ai-providers.json` from [models.dev](https://models.dev). This PR updates pricing, context windows, and model availability based on the latest data. New models meeting inclusion criteria (tool calling, pricing data, context limits) are added automatically. Deprecated models are detected based on cost-efficiency within the same model family. **Please review before merging** — verify no critical models were incorrectly deprecated. Co-authored-by: FelixMalfait <6399865+FelixMalfait@users.noreply.github.com> |
||
|
|
e632b7dbb9 |
fix(ai-billing): bill POST /rest/ai/generate-text usage to ClickHouse (#20066)
## Summary
`POST /rest/ai/generate-text` calls `generateText` and returns `usage`
to the client without emitting a `usageEvent`. Authenticated, gated only
by `PermissionFlagType.AI` — any workspace user with that permission
could call it in a loop without billing. Identified during the
2026-04-26 incident audit.
## What changed
- Inject `AiBillingService` into `AiGenerateTextController`.
- Add `@AuthUserWorkspaceId() userWorkspaceId: string` to source the
user-workspace identifier.
- Wrap the `generateText` call in `try { ... return ... } finally { ...
}` so billing fires even if the controller throws after Anthropic was
paid.
- Bill with `UsageOperationType.AI_WORKFLOW_TOKEN` and
`cacheCreationTokens: result.usage.inputTokenDetails?.cacheWriteTokens
?? 0`.
- Inner `try/catch` around the billing emit so a billing error can't
break the response.
- One-line module change: `AiGenerateTextModule` imports
`AiBillingModule` (NestJS DI requirement).
## Test plan
- [ ] Call `POST /rest/ai/generate-text` with a small prompt; verify a
`usageEvent` row appears in ClickHouse for the workspace with the
correct token count and `operationType = AI_WORKFLOW_TOKEN`.
- [ ] Call with a malformed model id that throws after the API key is
validated — verify no spurious billing call occurs (no Anthropic call
was made).
## Notes for review
- Response shape unchanged.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
|
||
|
|
5b3ee3f1a7 |
fix(ai-billing): bill thread title generation and tool-call repair (#20067)
## Summary Two `generateText` call sites were unbilled — identified during the 2026-04-26 incident audit: - **Thread title generation** (`AgentTitleGenerationService.generateThreadTitle`) — fires once per new chat thread; low-volume but completeness matters. - **Tool-call repair** (`repairToolCall` util, used inside `experimental_repairToolCall` callbacks) — can fire `MAX_STEPS` times per agent turn if a model gets stuck producing malformed tool calls. ## What changed - `AgentTitleGenerationService` — inject `AiBillingService`, expand `generateThreadTitle` signature to accept `workspaceId` and `userWorkspaceId`, bill in a `finally` block with `UsageOperationType.AI_CHAT_TOKEN`. Restructured so the no-default-model short-circuit happens before the `try`, avoiding a fake billing call. - `repair-tool-call.util.ts` — added an optional `billingContext` arg containing `aiBillingService`, `modelId`, `workspaceId`, `userWorkspaceId`, `operationType`. Wraps the `generateText` call in `try/finally`; bills in finally with the operation type provided by the caller. Optional so existing callers keep compiling. - `chat-execution.service.ts` — threads `billingContext` (`AI_CHAT_TOKEN`) into the repair callback. - `agent-chat.service.ts` — passes `workspaceId` and `thread.userWorkspaceId` to `generateThreadTitle`. - `agent-async-executor.service.ts` — TODO comment marking that the repair callback should thread billing once `executeAgent` accepts `workspaceId` (depends on the executeAgent-finally PR). ## Follow-up After the executeAgent-finally PR lands, `workspaceId` is in scope at the `experimental_repairToolCall` callback in `agent-async-executor.service.ts:198`. Thread `billingContext` through to fire repair-call billing for workflow agents too, and remove the TODO. Tiny follow-up. ## Test plan - [ ] Create a new chat thread; verify a `usageEvent` row is written for the title generation call. - [ ] Send a chat message that triggers tool-call repair (e.g. force a malformed tool call); verify a `usageEvent` row for the repair sub-call. ## Notes for review - `billingContext` arg made **optional** to allow incremental rollout — the executeAgent-finally PR plus a small follow-up will close the agent-async-executor side. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
875795cc30 |
feat(sentry): propagate workspace context to all spans (#20064)
## Summary After the 2026-04-26 token-usage incident, identifying the responsible workspace from a Sentry trace required a Postgres scavenger hunt — Vercel AI SDK auto-instrumentation captures token counts and model name but no twenty-specific identifiers, and that same gap exists for every other auto-instrumented span (HTTP outbound, Postgres queries, GraphQL resolvers, Redis, etc.). This PR plugs that gap globally, not just for AI: - A small utility (`packages/twenty-server/src/engine/core-modules/sentry/utils/sentry-workspace-context.util.ts`) that writes workspace identifiers onto Sentry's active isolation scope as a `twenty` context block plus filterable tags and a `Sentry.setUser` call. - Two hook points covering all server traffic: - **`WorkspaceAuthContextMiddleware`** — already runs after token hydration on the GraphQL, metadata, admin-panel, and REST routes. It now calls the utility once per authenticated request, before delegating to `withWorkspaceAuthContext`. - **`BullMQDriver.work` and `SyncDriver.processJob`** — every queue job now runs inside `Sentry.withIsolationScope` and applies workspace context from `job.data.workspaceId` (skipping silently for system jobs that don't carry one). - A `beforeSendSpan` hook in `instrument.ts` that reads the scope's `twenty` context block back and projects it onto every span as `twenty.workspace.id` and (when available) `twenty.user_workspace.id` — dotted-namespace naming consistent with OTel/Sentry conventions like `user.id` and `http.response.status_code`. Spans without a workspace context (unauthenticated traffic) pass through untouched. ## Why this shape Sentry's docs position `beforeSendSpan` as a per-span hook. The previous iteration set context only at AI-specific call sites, which left non-AI spans (DB queries, outbound HTTP, regular GraphQL queries, workflow steps not touching AI) entirely unenriched. Hooking the two existing global boundaries — auth middleware for HTTP/GraphQL/REST, and the queue driver `work()` callback for background jobs — covers every authenticated span across the app with no per-handler instrumentation. ## What's not in this PR AI-specific identifiers (`twenty.agent.id`, `twenty.thread.id`, `twenty.turn.id`, `twenty.workflow_run.id`) are out of scope here. They're useful additions but require either propagating the IDs through the call stack or a more fine-grained scope (per-step, per-turn) than the request/job boundary, which is best handled in follow-up PRs that target those specific call sites. ## Test plan - [ ] Make any authenticated GraphQL request locally and confirm the resulting span(s) in Sentry carry `twenty.workspace.id` and (for user-authenticated routes) `twenty.user_workspace.id`. - [ ] Make any authenticated REST request and confirm the same. - [ ] Trigger a queue job (chat stream, agent turn evaluation, workflow run, etc.) and confirm spans produced inside the worker carry `twenty.workspace.id`. - [ ] Confirm that DB and outbound HTTP spans produced under the request/job also carry the workspace tag — these previously had no twenty-specific identifiers. - [ ] In the Sentry UI, filter events by the `twenty.workspace.id` tag and confirm matching events appear. ## Notes for review - Sentry init lives in `instrument.ts`, loaded before Nest bootstraps, so `beforeSendSpan` runs outside Nest DI and reads context off the isolation scope rather than holding a service reference. - The middleware change is three lines; the BullMQ wrap is a single `Sentry.withIsolationScope` around the existing job handler body; the SyncDriver wrap mirrors it for the dev/test path. No new modules or DI providers. - The previous iteration's `AiCallContextService` and per-handler `setContext` / `withContext` calls have been removed in favor of these two hooks. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
bb5f294c5a |
[AI] Collapse NativeToolBinder to a single bind() entry (#20051)
The binder doesnt need an agent or full tool context -- just a model and options. Single `bind(model, options)` entry! Builds on #20022. |
||
|
|
f8c1ad5b3c |
Filtered upgrade logs when stopping before starting next instance segment (#20078)
# Introduction In a nutshell, added a more readable logs that relates that when performing a workspace fitlered workspace you can only browser the workspace commands sequence This PR is not a fix, but a log improvement As before when cross-upgrading a single workspace you would be facing a `instance` sync barrier error as not all your workspaces would have been updated Now logging and early returning instead of letting the guard hard throw ## Tests Created a dedicated test for instance prevention on filtered upgrade Standardized `migrationRecordToKey` usage across all upgrade integration tests suites |
||
|
|
95fee18126 |
fix(server): match IMAP \Noselect attribute case-insensitively (#20043)
## Summary
`ImapGetAllFoldersService.isMailboxSelectable` checked
`mailbox.flags?.has('\\Noselect')`, which is case-sensitive. Per [RFC
3501 §6.3.8](https://www.rfc-editor.org/rfc/rfc3501#section-6.3.8), IMAP
attribute names are case-insensitive — different servers spell the flag
differently:
| Server | Spelling |
|---|---|
| Dovecot | `\Noselect` |
| Stalwart | `\NoSelect` |
| Cyrus | `\Noselect` |
| RFC examples | `\NOSELECT` |
The previous check only caught Dovecot's spelling. On other servers,
virtual namespace placeholders (e.g. Stalwart's `Shared Folders` parent)
passed through `isMailboxSelectable` and got persisted as folders. When
`MessagingMessageListFetchJob` later ran, it issued `SELECT "Shared
Folders"`, the server correctly rejected it with `NO [NONEXISTENT]
Mailbox does not exist.`, and the entire message-list fetch failed for
the channel.
## Reproduction
1. Connect an IMAP account whose server advertises a `\NoSelect` (or
`\NOSELECT`) namespace placeholder. Stalwart Mail v0.15.x exhibits this
with shared mailboxes:
```
* LIST (\NoSelect) "/" "Shared Folders"
```
2. The folder discovery job persists it as a syncable folder.
3. `MessagingMessageListFetchJob` runs and fails:
```
IMAP: Error fetching message list: D0 SELECT "Shared Folders"
responseStatus: NO serverResponseCode: NONEXISTENT
```
## Fix
Iterate the flag set and lowercase-compare against `'\\noselect'`. This
matches every legal spelling without changing behavior for compliant
`\Noselect` clients.
```diff
private isMailboxSelectable(mailbox: ListResponse): boolean {
- return !mailbox.flags?.has('\\Noselect');
+ if (!mailbox.flags) return true;
+ for (const flag of mailbox.flags) {
+ if (flag.toLowerCase() === '\\noselect') return false;
+ }
+ return true;
}
```
## Test plan
- [x] Existing `\Noselect` test cases (`should not issue STATUS against
a \Noselect folder`, parent-reference preservation, Sent-folder
exclusion) still pass — the lowercase comparison subsumes them.
- [x] New parameterized test covers `\Noselect`, `\NoSelect`,
`\NOSELECT`, `\noselect` spellings against a Stalwart-style `Shared
Folders` namespace placeholder, asserting Twenty does **not** issue
`STATUS` on the placeholder and does **not** include it in the
discovered folder set.
---------
Co-authored-by: neo773 <62795688+neo773@users.noreply.github.com>
|
||
|
|
5b8804ff06 |
gmail extract body from deeply nested MIME parts (#19989)
/closes #19879 |
||
|
|
6545ca274c |
fix(messaging): refactor SentMessagePersistenceService (#20077)
refactored `SentMessagePersistenceService` to be thin wrapper over `saveMessagesAndEnqueueContactCreation` this fixes a bug where the old logic did not call match participants causing messages to not show up |
||
|
|
3db1af9a17 |
fix(logic-function): forward raw request body for HMAC signature verification (#20061)
## Summary - Add optional `rawBody?: string` to `LogicFunctionEvent` and forward it from the route trigger so HMAC-based webhook signatures (GitHub's `X-Hub-Signature-256`, Stripe, …) can be verified by user logic functions. - Update `github-connector`'s `getRawBodyForSignature` to prefer `event.rawBody` (with the existing string/base64/null fallbacks kept for older runtimes). ## Why GitHub computes `X-Hub-Signature-256` over the **raw bytes** of the request body. The receiver must verify against those exact bytes — key order, whitespace and unicode escaping all matter, so the parsed JSON body cannot be re-serialized to them. Today the route trigger calls `extractBody(request)` which returns the parsed object only. NestJS already preserves the raw body on `request.rawBody` (the app is bootstrapped with `rawBody: true` in `main.ts`), but it was never propagated into `LogicFunctionEvent`. As a result the github-connector's webhook handler always took the "raw body unavailable" branch and rejected every delivery (after #19961 / 962c2b3c14). With this change, signature verification can succeed end-to-end. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
577312f121 |
chore: sync AI model catalog from models.dev (#20073)
Automated daily sync of `ai-providers.json` from [models.dev](https://models.dev). This PR updates pricing, context windows, and model availability based on the latest data. New models meeting inclusion criteria (tool calling, pricing data, context limits) are added automatically. Deprecated models are detected based on cost-efficiency within the same model family. **Please review before merging** — verify no critical models were incorrectly deprecated. Co-authored-by: FelixMalfait <6399865+FelixMalfait@users.noreply.github.com> |
||
|
|
fb8b9cb86c |
Add admin avatars and app logos (#20001)
## Summary - Add user avatars and workspace logos to the admin general table and workspace member detail view - Show app icons in the admin app registrations table and reuse the shared application display component - Expose the needed avatar and logo fields through admin GraphQL queries and backend lookup/statistics services - Keep workspace fallback behavior consistent when no logo is set and clean up a few local table styling duplicates ## Testing - `./node_modules/.bin/tsc -p packages/twenty-front/tsconfig.json --noEmit --pretty false` - Manual UI verification of the admin general, workspace detail, and apps tables --------- Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
499067ae14 |
fix(logic-function): serialize LocalDriver layer builds with cache lock (#20054)
## Summary
Fixes a race condition in `LocalDriver` where concurrent `execute()`
calls for the same application can trash each other's layer build,
causing logic function executions to fail with either:
- `Error: ENOENT: process.cwd failed with error no such file or
directory, the current working directory was likely removed without
changing the working directory, uv_cwd` (the yarn-install child's `cwd`
got wiped mid-run), or
- `ENOENT: no such file or directory, open '…/<pkg>/<file>.js.map'`
raised from yarn's berry link step (files under the deps layer vanish
while yarn is extracting).
Both are thrown from `…/deps/<checksum>/.yarn/releases/yarn-4.9.2.cjs` —
i.e. the yarn process `copyYarnEngineAndBuildDependencies` spawns with
`cwd: buildDirectory`.
## Root cause
`LocalDriver.createLayerIfNotExist` (and `ensureSdkLayer`) both follow a
check-then-act pattern with no mutual exclusion:
```ts
if (await pathExists(depsNodeModulesPath)) return;
await fs.rm(depsLayerPath, { recursive: true, force: true });
await copyDependenciesInMemory(...);
await copyYarnEngineAndBuildDependencies(depsLayerPath); // spawns yarn with cwd=depsLayerPath
```
The deps layer path is shared across all `execute()` calls that match a
given `yarnLockChecksum`. Two concurrent callers (e.g. a webhook
invocation + a cron-triggered logic function firing while the first
run's layer is still being built) both see no `node_modules`, both
`fs.rm` the directory, and one's yarn child ends up with its `cwd` or
its extraction target gone.
## Fix
Wrap the critical sections with `CacheLockService.withLock` — the same
cache-backed lock the Lambda driver already uses for its layer/executor
builds:
- `createLayerIfNotExist` → lock key
`local-driver-deps-layer:${yarnLockChecksum ?? 'default'}`
- `ensureSdkLayer` → lock key
`local-driver-sdk-layer:${workspaceId}:${applicationUniversalIdentifier}`
A fast-path `pathExists` check is kept outside the lock (so warm
executions still skip lock acquisition entirely), and the existence +
staleness check is **repeated inside the lock** so followers no-op after
the leader finishes.
Lock parameters mirror the Lambda driver's layer-build lock (`ttl=120s`,
`retry=500ms`, `maxRetries=240`).
`cacheLockService` is now passed to `LocalDriver` from
`LogicFunctionDriverFactory` (it was already injected there for the
Lambda driver).
|
||
|
|
ca84d28157 |
[AI] ai usage line chart date gap filling (#20048)
https://github.com/user-attachments/assets/ecd37dfb-daae-41c3-8316-a9d923463eca |
||
|
|
d1a4902460 |
[AI] Drop 'serialization' from tool output naming (#20052)
didnt made sense anymore -- we dont really 'serialize' |
||
|
|
570038ad65 |
chore: sync AI model catalog from models.dev (#20045)
Automated daily sync of `ai-providers.json` from [models.dev](https://models.dev). This PR updates pricing, context windows, and model availability based on the latest data. New models meeting inclusion criteria (tool calling, pricing data, context limits) are added automatically. Deprecated models are detected based on cost-efficiency within the same model family. **Please review before merging** — verify no critical models were incorrectly deprecated. Co-authored-by: FelixMalfait <6399865+FelixMalfait@users.noreply.github.com> |
||
|
|
d74e3fa3b5 |
chore(server): bump current version to 2.2.0 (#20040)
## Summary We are releasing Twenty v2.2.0. This PR sets up the upgrade-version-command machinery for the new release line: - Promote `2.1.0` into `TWENTY_PREVIOUS_VERSIONS` (it just shipped) - Set `TWENTY_CURRENT_VERSION` to `2.2.0` - Reset `TWENTY_NEXT_VERSIONS` to `[]` - Refresh the `InstanceCommandGenerationService` snapshots to reflect the new current version (`2.2.0` / `2-2-` slug) - Update the failing-sequence-runner snapshot to include `2.2.0` in the covered versions list The `2-2/` upgrade-version-command module is already in place and wired into `WorkspaceCommandProviderModule`, so future upgrade commands targeting `2.2.0` can land directly under `2-2/` (or be generated against `--version 2.2.0`). |
||
|
|
80c0e5603d |
Move isPreInstalled applicationRegistration instance command to 2.1 (#20037)
## Summary The fast instance command adding `isPreInstalled` to `core.applicationRegistration` was added after 2.0 was released, so it must run as part of the 2.1 upgrade rather than 2.0. - Renamed `2-0/2-0-instance-command-fast-1776886452831-add-is-pre-installed-to-application-registration.ts` to `2-1/2-1-instance-command-fast-1776886452831-add-is-pre-installed-to-application-registration.ts` - Updated `@RegisteredInstanceCommand` version from `'2.0.0'` to `'2.1.0'` - Updated the import path in `instance-commands.constant.ts` The original timestamp (`1776886452831`) was kept; it is smaller than the existing 2.1 fast command timestamp, so this command will simply run first within the 2.1.0 batch. |
||
|
|
2ccc293f99 |
Gate export/import command menu items by permission flag (#19991)
## Summary - Hides the `exportRecords`, `exportView`, and `importRecords` command menu actions from users whose role does not hold the matching `EXPORT_CSV` / `IMPORT_CSV` permission flag. - Exposes the current user's role permission flags to `conditionalAvailabilityExpression` by adding `permissionFlags: Record<string, boolean>` to `CommandMenuContextApi`, mirroring how `featureFlags` is already accessible. - Adds a `2.1.0` workspace upgrade command that rewrites the three existing rows on every active/suspended workspace. ## Before <img width="1294" height="287" alt="Screenshot 2026-04-22 at 19 37 40" src="https://github.com/user-attachments/assets/11ca8635-14d7-40a0-9ca0-76329c54e3c6" /> ## After <img width="1283" height="285" alt="Screenshot 2026-04-22 at 19 32 25" src="https://github.com/user-attachments/assets/5e49fa8a-4541-42ee-96da-4c1de7d00aae" /> |
||
|
|
6c1c0737b0 |
Clarify registry tools vs native model tool binding (#20022)
## Intent This is a small foundation cleanup for the tool architecture. The main decision is: registry tools and native SDK/model tools are different things. - Registry tools have descriptors, schemas, catalog entries, and execute through `ToolExecutorService` - Native model tools are opaque AI SDK objects, bound directly into the model `ToolSet` - Surfaces still own their policy: chat, MCP, and workflow agents decide what they expose ## What changed - Removed `NATIVE_MODEL` from `ToolCategory` - Kept `ToolRegistryService` focused on registry-backed tools only - Moved native model tool binding through `NativeToolBinderService` - Reused native binding from chat instead of duplicating provider-specific web-search logic - Kept MCP local execution exclusions in a dedicated constant - Moved surface-specific constants into dedicated constant files ## What comes next - Move hardcoded chat app preloads, like Exa web search, into app/manifest metadata - Decide a clearer policy for local runtime tools like code interpreter and HTTP request - Gradually document the three tool shapes: registry tools, native model tools, and local runtime tools --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Félix Malfait <FelixMalfait@users.noreply.github.com> Co-authored-by: Félix Malfait <felix.malfait@gmail.com> |
||
|
|
251f5deab6 |
[breaking, deploy server first] fix(ai-chat): persist providerExecuted flag on tool parts (#20030)
## Summary Fixes Sentry errors of the form: > \`messages.3: \`tool_use\` ids were found without \`tool_result\` blocks immediately after: srvtoolu_…. Each \`tool_use\` block must have a corresponding \`tool_result\` block in the next message.\` ### Root cause When the model invokes a **provider-hosted tool** (e.g. Anthropic's native \`web_search\` — note the \`srvtoolu_\` ID prefix), the AI SDK marks the resulting \`UIMessagePart\` with \`providerExecuted: true\`. \`convertToModelMessages\` uses that flag to emit the tool_use/tool_result pair *inside the same assistant message* — the format Anthropic requires for server-side tools. Our \`AgentMessagePart\` persistence was dropping \`providerExecuted\` on the way to the DB (and re-hydration didn't know to set it). On the next turn, \`convertToModelMessages\` treated the rehydrated part as a client-side tool call, splitting it into \`assistant(tool_use)\` + \`user(tool_result)\` — which Anthropic then rejects with the error above. ### Fix - Add nullable \`providerExecuted BOOLEAN\` column on \`core.agentMessagePart\` via a fast instance command. - Surface the field on \`AgentMessagePartDTO\` (GraphQL). - Preserve it through \`mapUIMessagePartsToDBParts\` (server) and both \`mapDBPartToUIMessagePart\` mappers (server + frontend). - Include it in \`GET_CHAT_MESSAGES\` and \`GET_AGENT_TURNS\` selections. - Regenerate \`generated-metadata/graphql.ts\`. ### Backwards compatibility Existing rows have \`NULL providerExecuted\` and round-trip as the omitted flag — which is exactly the pre-fix behaviour for tool parts that were never provider-executed. Only *new* assistant messages using \`web_search\` (or other provider-hosted tools) will write \`true\`, and those are the only ones that were breaking. ## Test plan - [x] \`npx tsgo\` typecheck — server + front clean - [x] \`oxlint\` + \`prettier --check\` on all touched files — clean - [x] \`npx nx run twenty-server:database:migrate:prod\` runs the new instance command locally; \`providerExecuted\` column present on \`core.agentMessagePart\` - [x] Regenerated \`generated-metadata/graphql.ts\` — \`providerExecuted\` wired into both queries and \`AgentMessagePart\` type - [ ] Manual: start a chat with Anthropic web_search enabled, invoke the tool in turn 1, reply in turn 2 — should not throw the srvtoolu error 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |