Skip to content

Fix agent profile metadata lookup for production#30

Merged
walmat merged 167 commits intomainfrom
staging
Feb 26, 2026
Merged

Fix agent profile metadata lookup for production#30
walmat merged 167 commits intomainfrom
staging

Conversation

@walmat
Copy link
Copy Markdown

@walmat walmat commented Feb 26, 2026

Summary

  • Restores agentProfile metadata lookup that was removed during refactor
  • Production uses nameSingular: 'agentProfile' but the service only looked for 'agent', causing silent failure of agent auto-assignment on new policies
  • Now tries agentProfile first, falls back to agent

Test plan

  • Create a policy from the lead panel — agent should auto-assign
  • Check server logs for absence of "Agent object metadata not found" warning

🤖 Generated with Claude Code

walmat and others added 30 commits February 16, 2026 21:31
Introduces a generalized pipeline system that replaces hardcoded webhook workers
with a configurable approach. Supports push (webhook) and pull (scheduled fetch)
modes with field mapping, transforms, relation resolution, and dedup/upsert.

Backend: NestJS module with entities, GraphQL resolvers, BullMQ jobs, per-pipeline
rate limiting (100 req/min via ThrottlerService), and 100 tests across 12 suites.

Frontend: Settings UI for pipeline CRUD, field mapping editor, dry-run testing,
and run history.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduces a generalized pipeline system that replaces hardcoded webhook workers
with a configurable approach. Supports push (webhook) and pull (scheduled fetch)
modes with field mapping, transforms, relation resolution, and dedup/upsert.

Backend: NestJS module with entities, GraphQL resolvers, BullMQ jobs, per-pipeline
rate limiting (100 req/min via ThrottlerService), and 100 tests across 12 suites.

Frontend: Settings UI for pipeline CRUD, field mapping editor, dry-run testing,
and run history.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… table name

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes twentyhq#17411

---------

Co-authored-by: neo773 <neo773@protonmail.com>
Co-authored-by: neo773 <62795688+neo773@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Twenty's config system gives DB values priority over env vars, so we need
to rewrite crm.omniaagent.com -> staging-crm.omniaagent.com in the
configVariable table after replicating prod data.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
- Fixed "property entity not found" error when updating/creating a new
field and querying the same object repository just after
- Downgraded log type for unnecessary migration
Fixes twentyhq#17138

- Backend should have strict date/dateTime format validation
- FE in import csv is more permissive

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
## Context
Introducing "NewFieldDefaultConfiguration" to FIELDS widget
configurations
```typescript
{
  isVisible: boolean;
  viewFieldGroupId: string | null;
}
```

This configuration will define where a new field should be added (which
section) and its default visibility inside FIELDS widget views.
The new field position should always be at the end (meaning the last
position for the view fields OR the last position of a viewFieldGroup)

See "New fields" on this screenshot
<img width="401" height="724" alt="Layout V1"
src="https://github.com/user-attachments/assets/4969bcaa-f244-4504-8947-778a02c24c47"
/>
Create the necessary tooling to listen to metadata events and plug it to
the front components. Now we have a hot reload like experience when we
edit a component in an app.

## Backend

- Split `EventWithQueryIds` into `ObjectRecordEventWithQueryIds` and
`MetadataEventWithQueryIds`
- Publish metadata event batches to active SSE streams in
`MetadataEventsToDbListener`

## Frontend

- Create a metadata event dispatching pipeline: SSE metadata events are
grouped by metadata name, transformed into
`MetadataOperationBrowserEventDetail` objects, and dispatched as browser
`CustomEvents`
- Add `useListenToMetadataOperationBrowserEvent` hook for consuming
metadata operation events filtered by metadata name and operation type
- Rename `useListenToObjectRecordEventsForQuery` to
`useListenToEventsForQuery`, now accepting both
`RecordGqlOperationSignature` and `MetadataGqlOperationSignature`
- Implement `useOnFrontComponentUpdated` which subscribes to front
component metadata events and updates the Apollo cache when the
component is modified
- Add `builtComponentChecksum` to the front component query and appends
it to the component URL for browser cache invalidation
## Summary


https://github.com/user-attachments/assets/1e75cc9d-d9d2-4ef2-99f9-34450f5d8de7



Add background incremental type checking (`tsc --watch`) to the SDK dev
mode, so type regressions are caught when the generated API client
changes — without requiring a full rebuild of source files.

Previously, removing a field from the data model would regenerate the
API client, but existing front components/logic functions referencing
the removed field wouldn't surface type errors (since their source
didn't change, esbuild wouldn't rebuild them).

## What changed

- **Background `tsc --watch`**: a long-lived TypeScript watcher runs
alongside esbuild watchers, incrementally re-checking all files when the
generated client changes. Only logs on state transitions (errors appear
/ errors clear) to stay quiet.
- **Atomic client generation**: API client is now generated into a temp
directory and swapped in atomically, avoiding a race condition where
`tsc --watch` could see an empty `generated/` directory
mid-regeneration.
- **Step decoupling**: orchestrator steps no longer receive
`uploadFilesStep` directly. Instead, they use callbacks (`onFileBuilt`,
`onApiClientGenerated`), and each step manages its own `builtFileInfos`
state.
- **`apiClientChecksum` omitted from `ApplicationConfig`**: it's a
build-time computed value, same as `packageJsonChecksum`.
<img width="327" height="177" alt="image"
src="https://github.com/user-attachments/assets/02bd25bb-fa41-42b0-8d96-01c51bd4580c"
/>

<img width="529" height="452" alt="image"
src="https://github.com/user-attachments/assets/61f6e968-365b-4a5b-8f2b-a8419d6b1bd3"
/>
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
This fixes two edge cases for Gmail

- When policy was set to `SELECTED_FOLDERS` excluding root INBOX, it
missed label changes, so messages with newly applied labels weren't
imported until a full resync.

- Gmail thread replies by default by default do no inherit parent
message's label properties so thread context was also lost because only
individually labeled messages were returned, dropping earlier parts of
the conversation.

Fixed by subscribing to `labelAdded`/`labelRemoved` history events and
fetching full thread context when at least one message in a thread
carries a synced label. `ALL_FOLDERS` path is untouched.
Fixes:  twentyhq/core-team-issues#2027

We've replaced the DATE_TIME picker with DATE picker, and changed the
logic to filter for complete day period.



https://github.com/user-attachments/assets/ba7e1078-bab3-4c62-a803-d6a851f14b7d

---------

Co-authored-by: Arun kumar <arunkumar@Aruns-MacBook-Air.local>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
…ls (twentyhq#18019)

## Summary

- Replace all generic `"Unknown error"` fallback messages across the
server codebase with messages that include the actual error details
- The most impactful change is in `guard-redirect.service.ts`, which
handles OAuth redirect errors — non-`AuthException` errors (e.g.,
passport state verification failures) now show `"Authentication error:
<actual message>"` instead of the opaque `"Unknown error"`
- Gmail/Google error handler services now include the error message in
the thrown exception instead of discarding it
- Other catch blocks (workflow delay resume, migration runner rollback,
code interpreter, marketplace) now use `String(error)` for non-Error
objects instead of a static fallback

Fixes the class of issues reported in
twentyhq#17812, where a user saw
"Unknown error" during Google OAuth and had no way to diagnose the root
cause (which turned out to be a session cookie / SSL configuration
issue).

## Test plan

- [ ] Verify OAuth error flows (e.g., Google Auth with misconfigured
callback URL) now display the actual error message on the `/verify` page
instead of "Unknown error"
- [ ] Verify Gmail sync error handling still correctly classifies and
re-throws errors with descriptive messages
- [ ] Verify workflow delay resume failures include the error details in
the workflow run status


Made with [Cursor](https://cursor.com)

Co-authored-by: Cursor <cursoragent@cursor.com>
…wentyhq#18024)

## Summary

- Replace the character-stripping approach (`removeSqlDDLInjection`)
with standard PostgreSQL `escapeIdentifier` and `escapeLiteral`
functions across all workspace schema manager services
- Add missing identifier escaping to `createForeignKey` (was the only
method in the FK manager without it)
- Add allowlist validation for index WHERE clauses and FK action types
- Harden tsvector expression builder with proper identifier quoting

## Context

The workspace schema managers build DDL dynamically from metadata (table
names, column names, enum values, etc.). The previous approach stripped
all non-alphanumeric characters — safe but lossy (silently corrupts
values with legitimate special characters). The new approach uses
PostgreSQL's standard escaping:

- **Identifiers**: double internal `"` and wrap → `"my""table"` (same
algorithm as `pg` driver's `escapeIdentifier`)
- **Literals**: double internal `'` and wrap → `'it''s a value'` (same
algorithm as `pg` driver's `escapeLiteral`)

`removeSqlDDLInjection` is kept only for name generation (e.g.,
`computePostgresEnumName`) where stripping to `[a-zA-Z0-9_]` is the
correct behavior.

## Files changed

| File | What |
|------|------|
| `remove-sql-injection.util.ts` | Added `escapeIdentifier` +
`escapeLiteral` |
| `validate-index-where-clause.util.ts` | New — allowlist for partial
index WHERE clauses |
| 5 schema manager services | Replaced strip+manual-quote with
`escapeIdentifier`/`escapeLiteral` |
| `build-sql-column-definition.util.ts` | `escapeIdentifier` for column
names, validated `generatedType` |
| `sanitize-default-value.util.ts` | `escapeLiteral` instead of
stripping |
| `serialize-default-value.util.ts` | `escapeLiteral` for values,
`escapeIdentifier` for enum casts |
| `get-ts-vector-column-expression.util.ts` | `escapeIdentifier` for
field names in expressions |
| `sanitize-default-value.util.spec.ts` | Updated tests for escape
behavior |

## Test plan

- [x] All 64 existing tests pass across 6 test suites
- [x] `lint:diff-with-main` passes
- [x] TypeScript typecheck — no new errors
- [ ] Verify workspace sync-metadata still works end-to-end
- [ ] Verify custom object/field creation works
- [ ] Verify enum field option changes work


Made with [Cursor](https://cursor.com)

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
- Update migration command to handle case where workspace logo is
originated from workspace email and point to twenty-icons.com
- Update same logic for new workspaces
- Add feature-flag for all newly created workspaces
… operand filters (twentyhq#17564)

migration command in response to the fix :
twentyhq#17529 for the issue
twentyhq/core-team-issues#2027

---------

Co-authored-by: Arun kumar <arunkumar@Aruns-MacBook-Air.local>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…n in application (twentyhq#18037)

- add a new optional key `postInstallLogicFunctionUniversalIdentifier`
in applicationConfig
- seed postInstall function in create-twenty-app
- update execute:function options
- update doc
… secret

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…m conflicts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
walmat and others added 28 commits February 20, 2026 14:20
The wyw plugin has an explicit file allowlist — AudioLink was missing,
causing styled.div to be undefined at runtime on staging.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts:
#	packages/twenty-front/src/locales/generated/af-ZA.ts
#	packages/twenty-front/src/locales/generated/ar-SA.ts
#	packages/twenty-front/src/locales/generated/ca-ES.ts
#	packages/twenty-front/src/locales/generated/cs-CZ.ts
#	packages/twenty-front/src/locales/generated/da-DK.ts
#	packages/twenty-front/src/locales/generated/de-DE.ts
#	packages/twenty-front/src/locales/generated/el-GR.ts
#	packages/twenty-front/src/locales/generated/en.ts
#	packages/twenty-front/src/locales/generated/es-ES.ts
#	packages/twenty-front/src/locales/generated/fi-FI.ts
#	packages/twenty-front/src/locales/generated/fr-FR.ts
#	packages/twenty-front/src/locales/generated/he-IL.ts
#	packages/twenty-front/src/locales/generated/hu-HU.ts
#	packages/twenty-front/src/locales/generated/it-IT.ts
#	packages/twenty-front/src/locales/generated/ja-JP.ts
#	packages/twenty-front/src/locales/generated/ko-KR.ts
#	packages/twenty-front/src/locales/generated/nl-NL.ts
#	packages/twenty-front/src/locales/generated/no-NO.ts
#	packages/twenty-front/src/locales/generated/pl-PL.ts
#	packages/twenty-front/src/locales/generated/pseudo-en.ts
#	packages/twenty-front/src/locales/generated/pt-BR.ts
#	packages/twenty-front/src/locales/generated/pt-PT.ts
#	packages/twenty-front/src/locales/generated/ro-RO.ts
#	packages/twenty-front/src/locales/generated/ru-RU.ts
#	packages/twenty-front/src/locales/generated/sr-Cyrl.ts
#	packages/twenty-front/src/locales/generated/sv-SE.ts
#	packages/twenty-front/src/locales/generated/tr-TR.ts
#	packages/twenty-front/src/locales/generated/uk-UA.ts
#	packages/twenty-front/src/locales/generated/vi-VN.ts
#	packages/twenty-front/src/locales/generated/zh-CN.ts
#	packages/twenty-front/src/locales/generated/zh-TW.ts
#	packages/twenty-front/src/locales/ru-RU.po
Stamps the CarrierProduct commission value onto each Policy as a frozen LTV
snapshot at creation time. Existing policies keep their original LTV when
commission rates change — only future policies get the new value.

- Migration adds ltvAmountMicros/ltvCurrencyCode columns and CURRENCY field metadata
- Pipeline field mappings added for both HealthSherpa and Old CRM pipelines
- Both preprocessors look up CarrierProduct commission and inject _ltv* fields
- Pre-query hooks auto-fill LTV on manual/UI policy creation
- Date-aware backfill script for existing policies (scripts/backfill-policy-ltv.ts)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…arrier-product

Add LTV field to Policy from CarrierProduct commission
All staging pods were stuck Pending because the staging node has an
{environment: staging} taint but no pods tolerated it. Adds global
tolerations support to all 4 deployment templates and configures
the staging values with the required toleration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…arrier-product

Fix staging deploy: add tolerations for node taint
)

The ingestionPipeline table uses "targetObjectNameSingular" not
"targetObject" for the target object column.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Add policy.updateOne and policy.updateMany pre-query hooks that
  auto-derive LTV from CarrierProduct commission when carrier or
  product changes on a policy
- Fix RecordInlineCell: replace unused useRecoilCallback import with
  useStore from jotai (was causing ReferenceError on every inline cell)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces the partially-reverted Jotai migration artifacts with
the known-good versions from main.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ustomizations

Uses the clean Jotai-migrated versions from upstream (useStore from
jotai, useRecoilComponentFamilyValueV2, isSelectedItemIdComponentFamilyState)
with our additions (FieldDependencyContext cascade clear, ONE_TO_MANY
sub-menu filter check).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ing record

When the UI sends only carrierId or only productId in an updateOne
payload, the hook now fetches the existing policy record (bypassing
permission checks) to get the other ID before looking up the
CarrierProduct commission. This ensures LTV is stamped even when
fields are set one at a time or hidden from agents.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tate

selectedItemIdComponentState was migrated to createComponentStateV2
but this file still used useRecoilComponentValue (Recoil). Switch to
useRecoilComponentValueV2 to match.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tate

selectedItemIdComponentState was migrated to createComponentStateV2
but this file used useRecoilComponentValue. Added useRecoilComponentValueV2
for the V2 state while keeping useRecoilComponentValue for the old-style
selectors still in use.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…in workspace context

The workspace ORM returns CURRENCY fields as nested objects (e.g.
commission.amountMicros) not flat columns (commissionAmountMicros).
Also wraps all policy pre-query hook ORM calls in executeInWorkspaceContext.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pre-query hooks modify the user's mutation payload, which fails when the
user's role doesn't have permission to the LTV field. Post-query hooks
run after the mutation succeeds and do a separate bypassed-permissions
update to stamp LTV, so it works regardless of role field settings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The workspace repository.update() goes through formatData which expects
composite CURRENCY fields as nested objects (ltv.amountMicros), not flat
column names (ltvAmountMicros).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ezone

- Post-query hooks now derive policy name as "{Carrier} - {Product}"
  using the same pattern as ingestion, with bypassed permissions
- Restore submittedDate auto-set on policy creation (accidentally dropped
  during LTV migration) using the workspace member's configured timezone
- Falls back to America/New_York when timezone is 'system' or unset
- Rename stamp-policy-ltv to enrich-policy-after-save (handles both
  name and LTV in a single update)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Name was previously derived in post-query hooks, causing stale values
in the UI (one step behind). Moving to pre-query hooks ensures the
name is set before the mutation, so the GraphQL response includes it.

- Add name derivation to create-one and create-many pre-query hooks
- Create update-one pre-query hook (fetches existing record for missing field)
- Create update-many pre-query hook
- Extract buildPolicyDisplayName into shared utility
- Simplify enrich-policy-after-save to LTV-only

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pre-query hooks were adding submittedDate and agentId to the mutation
payload, which fails field-level permission checks when the member role
doesn't have write access to those fields.

Moved submittedDate and agentId auto-setting to post-query hooks where
they use bypassed permissions (same pattern as LTV). Pre-query hooks
now only handle name derivation from carrier + product.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The ErrorBoundary in WidgetRenderer was silently catching errors and
showing "Invalid Configuration" without any telemetry. This adds an
onError callback that logs to console and sends to Sentry with widget
context (type, id, configuration) to help debug member role issues.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ilComponentValue

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The DATE_OVERRIDES key was 'united health fellowship' but the actual
carrier name is 'Universal Health Fellowship'. Also broadened the
product pattern from /health sharing/i to /./i since UHF products
are named Thrive/Standard/STx, not "health sharing".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Match auto insurance by carrier name (geico, the general) in addition
to product name, since Geico products are named Standard/Preferred/
Non-Standard without the word "auto".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Production uses nameSingular 'agentProfile' but the refactored service
only looked for 'agent', causing silent failure of agent auto-assignment.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.