## Summary
This PR adds a database migration command to backfill standard skills
for existing workspaces and improves the skill loading tool to provide
dynamic, workspace-specific skill availability information instead of
hardcoded skill names.
## Key Changes
- **New Migration Command**: Added `BackfillStandardSkillsCommand`
(v1.22.0) that:
- Identifies missing standard skills in existing workspaces
- Compares workspace skills against the standard skill definitions
- Creates missing skills using the workspace migration service
- Supports dry-run mode for safe testing
- Properly logs all operations and handles failures
- **Enhanced Skill Loading Tool**: Updated `createLoadSkillTool` to:
- Accept a new `listAvailableSkillNames` function parameter
- Dynamically fetch available skills from the workspace instead of using
hardcoded skill names
- Provide accurate, context-aware error messages when skills are not
found
- Gracefully handle workspaces with no available skills
- **Service Updates**: Modified skill tool implementations in:
- `McpProtocolService`: Integrated `findAllFlatSkills` to list available
skills
- `ChatExecutionService`: Integrated `findAllFlatSkills` to list
available skills
- **Module Registration**: Added `BackfillStandardSkillsCommand` to the
v1.22 upgrade module
- **Test Updates**: Updated `McpProtocolService` tests to mock the new
`findAllFlatSkills` method
## Implementation Details
The backfill command uses the existing workspace migration
infrastructure to safely create skills, ensuring consistency with other
metadata operations. The skill availability messaging now reflects the
actual skills present in each workspace, improving user experience when
skills are not found.
https://claude.ai/code/session_012fXeP3bysaEgWsbkyu4ism
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- Introduces a new `application-logs` core module with a driver pattern
(disabled/console/clickhouse) to capture and persist logic function
execution logs
- Adds a ClickHouse `applicationLog` table with per-line log storage,
30-day TTL, and `ORDER BY (workspaceId, timestamp, applicationId,
logicFunctionId)`
- Surfaces application logs in the existing frontend audit logs table as
a new "Application Logs" source with dedicated columns (Function,
Timestamp, Level, Message, Execution ID)
## Details
**Write path**: `LogicFunctionExecutorService.handleExecutionResult()`
parses the multi-line log string from driver output into individual `{
timestamp, level, message }` entries, generates an execution UUID, and
passes them to `ApplicationLogsService.writeLogs()` which delegates to
the configured driver.
**Driver pattern**: Follows the exception-handler module style (Symbol
injection token + `forRootAsync` dynamic module). Three drivers:
- `DISABLED` (default) — no-op, prevents information leaking
- `CONSOLE` — structured stdout logging with level-based `console.*`
calls
- `CLICKHOUSE` — inserts rows into the `applicationLog` ClickHouse table
**Read path**: Extends the existing event-logs module by adding
`APPLICATION_LOG` to the `EventLogTable` enum, table name mapping, and
normalization logic.
**Config**: New `APPLICATION_LOG_DRIVER_TYPE` environment variable
(default: `DISABLED`).
- disable publish with existing version
- disable installation of app version already installed
---------
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
## Summary
Fixes#19422.
Self-hosted users hitting "No AI models are available" after configuring
API keys via the admin panel were victims of a stale
`AiModelRegistryService` cache. Only the four `addAiProvider` /
`removeAiProvider` / `addModelToProvider` / `removeModelFromProvider`
mutations called `refreshRegistry()` — setting an API key through
`set/update/deleteDatabaseConfigVariable` left the registry pointing at
the pre-mutation provider state.
Rather than patch each mutation site (and re-introduce the same class of
bug on the next one), the registry now invalidates lazily based on the
LLM config-group hash, mirroring the pattern `WebSearchDriverFactory`
already uses via `DriverFactoryBase`. Any mutation to an LLM-tagged
config variable is picked up automatically on the next read — callers
never have to remember to refresh.
- Extracted `getConfigGroupHash` into a shared util reused by both
`DriverFactoryBase` and `AiModelRegistryService` (and switched the hash
to `JSON.stringify` so object-typed config vars like `AI_PROVIDERS`
actually contribute meaningfully).
- `AiModelRegistryService` gates all internal `Map` access behind
private getters that call `ensureFresh()`, so future read paths can't
accidentally observe stale state. Build path uses underscored backing
fields directly to avoid recursing.
- Dropped the now-redundant `refreshRegistry()` public method and its
four call sites in `admin-panel.resolver.ts`.
## Test plan
- [x] `nx typecheck twenty-server` passes
- [x] Existing admin-panel + ai-models specs pass (79 tests)
- [ ] Manual: on a self-hosted instance, set `OPENAI_API_KEY` (or
another provider key) via the admin panel `setDatabaseConfigVariable`
mutation and confirm AI features work without a server restart
- [ ] Manual: existing flows (`addAiProvider`, `addModelToProvider`,
etc.) still pick up new providers on the next read
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
# Introduction
Persist in the db the registry computed name that contains
version_classname_timestamp to be consistent with the instance command
pattern too
# Introduction
This PR introduces the slow instance commands pattern, that allow
migrating data in prior of the schema migration, that would fail if not.
Slow instance commands runs after the fast instance commands and before
the workspace commands.
On twenty instance that do not has any active or suspended workspace the
data migration part is skipped but the migration still runs, especially
for fresh installs
We were previously hacking through typeorm transaction system to gain
such granularity using save points:
```ts
export class AddPayloadToCommandMenuItem1775129635528
implements MigrationInterface
{
name = 'AddPayloadToCommandMenuItem1775129635528';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "core"."commandMenuItem" ADD "payload" jsonb`,
);
const savepointName =
'sp_add_payload_check_constraint_to_command_menu_item';
try {
await queryRunner.query(`SAVEPOINT ${savepointName}`);
await addPayloadCheckConstraintToCommandMenuItem(queryRunner);
await queryRunner.query(`RELEASE SAVEPOINT ${savepointName}`);
} catch (e) {
try {
await queryRunner.query(`ROLLBACK TO SAVEPOINT ${savepointName}`);
await queryRunner.query(`RELEASE SAVEPOINT ${savepointName}`);
} catch (rollbackError) {
// oxlint-disable-next-line no-console
console.error(
'Failed to rollback to savepoint in AddPayloadToCommandMenuItem1775129635528',
rollbackError,
);
throw rollbackError;
}
// oxlint-disable-next-line no-console
console.error(
'Swallowing AddPayloadToCommandMenuItem1775129635528 error',
e,
);
}
}
```
It was afterwards re-applied within an workspace commands, it was hacky
and missleading for the self host having false positive in logs
## New pattern
Generate the slow instance command
```
npx nx database:migrate:generate twenty-server -- --name add-foo-bar-columns --type slow
```
```ts
import { DataSource, QueryRunner } from 'typeorm';
import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
import { SlowInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/slow-instance-command.interface';
@RegisteredInstanceCommand('1.21.0', 1775640902366, { type: 'slow' })
export class AddPrastoinColToWorkspaceSlowInstanceCommand implements SlowInstanceCommand {
async runDataMigration(dataSource: DataSource): Promise<void> {
// TODO: implement data backfill before the DDL migration
}
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE "core"."workspace" ADD "prastoin" character varying');
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE "core"."workspace" DROP COLUMN "prastoin"');
}
}
```
### Why `run-instance-commands` and `upgrade` remain separate commands
These two commands serve fundamentally different purposes with
incompatible scoping semantics:
The run-instance-commands iterates over all the legacy typeorm and the
instance commands of all versions, used for database init and so on. we
could be centralizing both but the readability tradeoff isn't worth it
In the future thanks to the cross-version pattern we will be able to
centralize them but not right now
Please note that by default the `run-instance-commands` only run the
fast instance commands which is expected for our cloud prod CD
## Summary
- **Config as source of truth**: `~/.twenty/config.json` is now the
single source of truth for SDK authentication — env var fallbacks have
been removed from the config resolution chain.
- **Test instance support**: `twenty server start --test` spins up a
dedicated Docker instance on port 2021 with its own config
(`config.test.json`), so integration tests don't interfere with the dev
environment.
- **API key auth for marketplace**: Removed `UserAuthGuard` from
`MarketplaceResolver` so API key tokens (workspace-scoped) can call
`installMarketplaceApp`.
- **CI for example apps**: Added monorepo CI workflows for `hello-world`
and `postcard` example apps to catch regressions.
- **Simplified CI**: All `ci-create-app-e2e` and example app workflows
now use a shared `spawn-twenty-app-dev-test` action (Docker-based)
instead of building the server from source. Consolidated auth env vars
to `TWENTY_API_URL` + `TWENTY_API_KEY`.
- **Template publishing fix**: `create-twenty-app` template now
correctly preserves `.github/` and `.gitignore` through npm publish
(stored without leading dot, renamed after copy).
## Test plan
- [x] CI SDK (lint, typecheck, unit, integration, e2e) — all green
- [x] CI Example App Hello World — green
- [x] CI Example App Postcard — green
- [x] CI Create App E2E minimal — green
- [x] CI Front, CI Server, CI Shared — green
# Introduction
Migrating the workspace commands to the decorator version + timestamp
listing as for the instance commands
We've now been able to remove the upgrade command abstraction where we
needed to import all modules and order them
Now they're dynamically retrieved at upgrade runtime, sorted by
timestamp
## Instance and workspace commands name
The name is computed from the command metadata `version` `className` and
`timestamp` we have a duplicate validation at module init from the
unified registry
# Size issue
```
Yarn install Lambda failed: {"errorType":"Error","errorMessage":"yarn install failed: ➤ Y/tmp/3bac8baafe6354db414389434726b02c/nodejs/node_modules/tar ENOSPC: no space left on device, write","➤ YN0000: └ Completed in 3s 57ms","➤ YN0000: · Failed with errors in 11s 684ms",""," at runYarnInstall (file:///var/task/index.mjs:48:11)"," at process.processTicksAndRejections (node:internal/process/task_queues:105:5)"," at async Runtime.handler (file:///var/task/index.mjs:119:5)"]}
```
# target esnext
setting the same target for each logic function build funnels
- sdk
- local driver
- lambda driver
- Adds a payload JSON column to `CommandMenuItem` and introduces a
unified `NAVIGATION` engine component key that replaces all individual
GO_TO_* keys
- Navigation commands now use the payload to determine their target
(either an objectMetadataItemId or a path), making navigation commands
dynamic and eliminating the need for a hardcoded engine key per object
- Includes a 1.21 upgrade command (refactor-navigation-commands) that
migrates existing GO_TO_* items to NAVIGATION items with the appropriate
payload, and applies a CHECK constraint enforcing payload coherence
https://github.com/user-attachments/assets/4d305ba2-ae0b-4556-bb0e-e9d899777350
TODO: In a second PR, create the sync between object metadata items and
the navigation command menu items
- Object metadata item created or enabled -> Create navigation command
- Object metadata item deleted or disabled -> Delete associated
navigation command
In another PR:
- Allow `label`, `shortLabel` and `icon` to resolve the
`navigateToObjectMetadataItem` dynamically in their interpolation
instead of being hardcoded in the command menu item
- Make the icon dynamic in the command menu items as the label so that
we can resolve ${navigateToObjectMetadataItem.icon} at runTime -> This
way we won't need to keep update the command menu item icon when we
update the objectMetadataItem icon
**Optimize workflow cron jobs: partition workspaces and use raw
queries**
- Split all 3 workflow cron jobs (WorkflowRunEnqueueCronJob,
WorkflowHandleStaledRunsCronJob, WorkflowCleanWorkflowRunsCronJob) to
process only 1/10th of workspaces per invocation using minute-based
partitioning, reducing per-run load
- Replace ORM repository + workspace context loading with raw SQL
queries in WorkflowRunEnqueueCronJob and
WorkflowHandleStaledRunsCronJob, avoiding costly cache/metadata
hydration for a simple existence check
# Introduction
As for the instance commands we want to keep a track of what has been
run for the workspace commands
Note that the history will be updated only when the workspace command
has been run through the upgrade directly and not when run atomically
## What's next
Later we will use this history in order to determine the current
workspace's version and instance's version getting rid of the version in
database, that will be the last stone
## Summary
Fixes#19377
- **Redis health**: The hit rate calculation divides by zero when both
`keyspace_hits` and `keyspace_misses` are `"0"` (common on fresh
instances). The string `"0"` is truthy so the guard
`statsData.keyspace_hits ? ...` doesn't catch this case, resulting in
`0/0 = NaN`. Fixed by computing the total first and checking it's a
valid non-zero number.
- **Database health**: The cache hit ratio query returns `null` when
`pg_statio_user_tables` is empty (no user tables). `parseFloat(null)` →
`NaN`. Fixed by adding a null check.
## Test plan
- [ ] Verify health indicators display correctly on a fresh instance
with no Redis keyspace activity
- [ ] Verify health indicators display correctly on a database with no
user tables
- [ ] Existing tests in `redis.health.spec.ts` still pass
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: easonysliu <easonysliu@tencent.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
# Introduction
Now only using typeorm to generate migrations up and down statement
We handle and maintain our own migration table history
## What's new
Now all the instance commands will live within the same module and
folder than the upgrade commands
Sequentiality comes from the timestamp located in the filename
Same sequentiality also applies to the workspace commands in the future,
for the moment still expected a as code explicit declaration
( below screen is an example see below section )
<img width="1382" height="634" alt="image"
src="https://github.com/user-attachments/assets/5610a246-4eae-485e-99f4-98fb89ad5ac8"
/>
## Existing 1.21 migrations
We won't start following this pattern in 1.21 yet at least not with the
migration that has already been released as typeorm migrations in cloud
production as they would rerun
## Small duplication
Duplicating the legacy typeorm and instance commands run in the
`run-instance-commands` to avoid any merge of interest for the moment
## Concurrency
Not handling any run in parrallel of the upgrade for the moment
## Summary
- **Inline email reply**: Replace external email client redirects
(Gmail/Outlook deeplinks) with an in-app email composer. Users can reply
to email threads directly from the email thread widget or via the
command menu.
- **SendEmail GraphQL mutation**: New backend mutation that reuses
`EmailComposerService` for body sanitization, recipient validation, and
SMTP dispatch via the existing outbound messaging infrastructure.
- **Side panel compose page**: Command menu "Reply" action now opens a
side-panel compose email page with pre-filled To, Subject, and
In-Reply-To fields.
### Backend
- `SendEmailResolver` with `SendEmailInput` / `SendEmailOutputDTO`
- `SendEmailModule` wired into `CoreEngineModule`
- Reuses `EmailComposerService` + `MessagingMessageOutboundService`
### Frontend
- `EmailComposer` / `EmailComposerFields` components
- `useSendEmail`, `useReplyContext`, `useEmailComposerState` hooks
- `useOpenComposeEmailInSidePanel` + `SidePanelComposeEmailPage`
- `EmailThreadWidget` inline Reply bar with toggle composer
- `ReplyToEmailThreadCommand` now opens side-panel instead of external
links
### Seeds
- Added `handle` field to message participant seeds for realistic email
addresses
- Seed `connectedAccount` and `messageChannel` in correct batch order
## Test plan
- [ ] Open an email thread on a person/company record → verify
"Reply..." bar appears below the last message
- [ ] Click "Reply..." → composer opens inline with pre-filled To and
Subject
- [ ] Type a message and click Send → email is sent via SMTP, composer
closes
- [ ] Use command menu Reply action → side panel opens with compose
email page
- [ ] Verify Send/Cancel buttons work correctly in side panel
- [ ] Test with Cc/Bcc toggle in composer fields
- [ ] Verify error handling: invalid recipients, missing connected
account
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary
- Introduces a pluggable `WebSearchDriver` abstraction (interface,
factory, service, module) so web search is no longer tied to native
provider tools (Anthropic/OpenAI)
- **Exa** is the first driver implementation with support for
category-filtered search (company, people, news, research paper, etc.) —
particularly useful for CRM workflows
- Per-query billing for both Exa ($0.007/query) and native provider
surcharges ($0.01/query for Anthropic/OpenAI) via the existing
`USAGE_RECORDED` pipeline
- New config variables: `WEB_SEARCH_DRIVER` (EXA/DISABLED),
`EXA_API_KEY`, `WEB_SEARCH_PREFER_NATIVE` (default false — prefers Exa
over native when both available)
- `WEB_SEARCH` operation type added for usage tracking and Stripe
metering
### Architecture
```
WebSearchDriver (interface)
├── ExaDriver — Exa neural search with category support
└── DisabledDriver — throws when search is disabled
WebSearchDriverFactory (extends DriverFactoryBase)
└── creates driver based on WEB_SEARCH_DRIVER config
WebSearchService (facade)
├── search(query, options?, billingContext?)
├── isEnabled()
└── emits USAGE_RECORDED events per query
WebSearchTool (Tool implementation)
└── registered in ActionToolProvider, available via tool catalog
```
### Native search billing gap fixed
Anthropic and OpenAI both charge $0.01/search on top of token costs. The
token costs were already billed, but the per-call surcharge was not.
Added `countNativeWebSearchCallsFromSteps` utility +
`billNativeWebSearchUsage` to `AiBillingService`, wired into both chat
and workflow agent paths.
## Test plan
- [ ] Set `WEB_SEARCH_DRIVER=EXA` + `EXA_API_KEY=...` and verify AI chat
can search the web
- [ ] Verify category parameter works (ask about a specific
company/person)
- [ ] Set `WEB_SEARCH_DRIVER=DISABLED` and verify search tool is not
exposed
- [ ] Set `WEB_SEARCH_PREFER_NATIVE=true` with Anthropic model and
verify native search is used
- [ ] Verify usage events are emitted in ClickHouse for both Exa and
native search paths
- [ ] Verify existing billing tests pass (`npx jest
ai-billing.service.spec.ts`)
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
## Summary
- **Remove `ExecuteToolResult` wrapper** — `execute_tool` is now a
transparent dispatcher that returns the raw `ToolOutput` from underlying
tools. No more `{ toolName, result }` envelope.
- **Type the entire execution chain as `Promise<ToolOutput>`** — from
`ToolExecutorService.dispatch()` through `resolveAndExecute()` to
`execute_tool.execute()`. Zero `Promise<unknown>` remaining in the tool
layer.
- **Use `Extract<ToolExecutionRef, ...>`** for dispatch methods,
enabling exhaustive switch checking and removing `as never` casts.
- **Relax `ToolOutput.result` to accept `null`** — removes `??
undefined` hacks at the boundary with logic function results.
- **Enforce 1-export-per-file** across tool type/interface files (split
`tool-descriptor.type.ts`, `tool-provider.interface.ts`, `tool.type.ts`,
`tool-output.type.ts`, `tool-executor.service.ts`).
- **Simplify error handling** — `wrapWithErrorHandler` and all
meta-errors (tool not found, tool excluded) now return consistent
`ToolOutput` shape with `error` as a plain string.
- **Frontend reads output directly** — removed `unwrapToolOutput`
utility; `ToolStepRenderer` and `ThinkingStepsDisplay` extract
`message`/`error` from the raw output with simple type guards.
- **Add permission error detection** for email tools via
`isInsufficientPermissionsError`, guiding the AI model to suggest
account reconnection instead of hallucinating about visibility settings.
## Test plan
- [ ] AI chat tool calls return visible output (not "null") in the UI
- [ ] Tool errors display correctly in the JSON tree
- [ ] Email draft/send tools return actionable permission errors
- [ ] Code interpreter output renders correctly
- [ ] Thinking steps display tool outputs properly
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Bug fixes exposed by always-on direct execution
1. GraphQL spec compliance — data[field] = null on resolver error
direct-execution.service.ts — Changed from Promise.allSettled (which
lost the responseKey on rejection) to Promise.all with per-field
try/catch; errors now set data[responseKey] = null per spec
2. Empty object arguments skipped (extractArgumentsFromAst)
extract-arguments-from-ast.util.ts — Removed isEmptyObject check;
filter: {}, data: {} now correctly passed to resolvers instead of
silently dropped (which caused permissions to never be checked)
3. orderBy: {} factory default treated as "no ordering"
direct-execution.service.ts — Before calling the resolver, strips
orderBy: {} and orderByForRecords: {} (empty-object factory defaults
that mean "no ordering")
assert-find-many-args.util.ts / assert-group-by-args.util.ts — Accept {}
for orderBy without throwing
4. orderBy: { field: '...' } object auto-coerced to [{ field: '...' }]
array
direct-execution.service.ts — Applies GraphQL list coercion: a
non-array, non-empty orderBy object is wrapped in an array before
assertion and resolver call
5. totalCount and aggregate fields returned as strings from PostgreSQL
graphql-format-result-from-selected-fields.util.ts — Added
coerceAggregateValue that parses numeric strings to numbers for
totalCount, sum*, avg*, min*, max*, count*, percentageOf* fields
Test updates
nested-relation-queries.integration-spec.ts — Updated expected error
message from Yoga schema-validation message to direct execution resolver
message
~30 snapshot files — Updated to reflect direct execution's error
messages (different from Yoga schema-validation messages for input type
errors)
- WorkflowCronTriggerCronJob was querying every active workspace (~700)
sequentially every minute to find cron triggers, causing regular CPU
spikes to 100% on worker pods
- Added a Redis hashset cache that stores cron triggers. On cache miss
(TTL expired, cold start, or explicit invalidation), a full scan
rebuilds the cache
- Creating/deleting a new cron trigger updates the cache, only if exists
- simplify the base application template
- remove --exhaustive option and replace by a --example option like in
next.js https://nextjs.org/docs/app/api-reference/cli
- Fix some bugs and logs
- add a post-card app in twenty-apps/examples/
## Summary
Rebased version of #19051 with all review comments addressed. Clean
branch on latest main, lint/typecheck/tests passing.
### Changes from original PR
- AI can now create, update, and delete **view filters**
(`ViewFilterToolsFactory`) and **view sorts** (`ViewSortToolsFactory`)
- `create_view` now accepts `calendarFieldName`, `calendarLayout`, and
`fieldNames` to configure views at creation time
- Three new standard skills: `view-building`, `view-filters-and-sorts`,
`custom-objects-cleanup`
- `workspace-demo-seeding` skill reworked to keep standard objects and
enrich them with custom fields
- Cache invalidation for nav menu items when object `isActive` changes
- Dashboard tool descriptions improved (RECORD_TABLE widget workflow)
### Review comments addressed (all 10 from #19051)
1. **Sentry + Cubic**: Calendar field DATE/DATE_TIME validation — added
`resolveCalendarFieldMetadataId` using `isFieldMetadataDateKind`
2. **Cubic**: "navigate tool" → "navigate_app tool" in skill metadata
(all 7 occurrences)
3. **Copilot**: KANBAN views now require `mainGroupByFieldName` — throws
clear error if missing
4. **Copilot**: CALENDAR views now require both `calendarFieldName` and
`calendarLayout` — validated before DB call
5. **Copilot**: Mock field fixtures include `type` property (DATE_TIME,
TEXT, SELECT)
6. **Copilot**: `ViewFilterValue` type assertion instead of unsafe `as
string` casts (3 locations)
7. **FelixMalfait**: Removed
`NavigationMenuItemObjectDeactivationListener` — replaced with cache
invalidation
8. **FelixMalfait**: Consolidated `ViewFilterToolProvider` and
`ViewSortToolProvider` into single `ViewToolProvider`
9. Removed `VIEW_FILTER` and `VIEW_SORT` from `ToolCategory` enum
(merged into `VIEW`)
10. Removed stale `existingFeatureFlagsMap` param incompatible with
current main
## Test plan
- [x] `npx nx lint:diff-with-main twenty-server` — passes
- [x] `npx nx typecheck twenty-server` — passes
- [x] `view-tools.factory.spec.ts` — all 20 tests pass (including 3 new
validation tests)
Supersedes #19051https://claude.ai/code/session_01QPV74NU6vzmJb32e4i899E
---------
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- **Make app-synced objects searchable**: `isSearchable` was hardcoded
to `false` and the `searchVector` field was missing the `GENERATED
ALWAYS AS (...)` expression, causing all records to have a `NULL` search
vector and be excluded from search results. Fixed by defaulting
`isSearchable` to `true` (configurable via the object manifest),
computing the `asExpression` from the label identifier field, and
allowing the update-field-action-handler to handle the `null` → defined
`asExpression` transition.
- **Make `isSearchable` updatable on an object**: The property had
`toCompare: false` in the entity properties configuration, so updates
via the API were silently ignored and never persisted. Fixed by setting
`toCompare: true`.
Auto merged passed to fast on
https://github.com/twentyhq/twenty/pull/19271
Try catch has been added while debugging an integration test suite
covering the workspace creation that would fail due to sdk generation
failure
As they're run synchronously in integration tests they would stop the
workspace creation process
If the proposed fix here isn't enough I'll wrapp the run sync test to be
catching any unexpected issue