Main - #215
Closed
franckh-stack wants to merge 70 commits into
Closed
Conversation
release: promote main
…es--release chore: release release
release: promote main
release: promote main
release: promote main
release: promote main
release: promote main
release: promote main
release: promote main
release: v1.8.1
release: v1.8.2
release: v1.9.0
release: v1.10.0
release: v1.11.0
release: v1.12.0
release: v1.13.0
release: v1.14.0
release: v1.15.0
release: v1.15.1
release: v1.15.2
release: v1.15.3
Multi-stage build (deps/build/runtime) on oven/bun:1.3.12. Two fixes
verified empirically against the plan's first draft:
- apps/api's postinstall (chmod-trpc-binary.mjs) needs
apps/api/scripts/ present in the deps stage, not just package.json,
or bun install fails.
- @crm/db's postinstall runs `prisma generate`, whose
prisma.config.ts calls env("DATABASE_URL") eagerly and throws if
unset -- even though generate never opens a connection. Deps stage
sets a placeholder DATABASE_URL for install; the real value is
supplied at `docker run` time and overrides it.
Ignore file named apps/api/Dockerfile.dockerignore, not
apps/api/.dockerignore: Task 3's build command uses the repo root as
build context (docker build -f apps/api/Dockerfile ... .), and Docker
only honors a Dockerfile-specific ignore file when it is named
<path-to-dockerfile-relative-to-context>.dockerignore at the context
root. Verified with a canary file: apps/api/.dockerignore was
silently ignored (canary leaked into the image), Dockerfile.dockerignore
excludes node_modules/dist/.git as intended.
Verified: image builds, runs against the real crm_trycompai database
(137.74.172.178), connects, and serves GET /health -> 503
{"status":"error","database":"down"} -- expected since Task 2 only
provisioned an empty database, migrations run in Task 6.
…tual default/deploy branch
…way, add Dockerfile
Vigieproc fork adaptation -- avoids a Vercel account dependency for the
main agent's LLM calls, cf. scripts/SPEC-fork-trycompai-crm.md Task 5.
eve already accepts a raw AI SDK LanguageModel in place of a Gateway
model-id string (PublicAgentStaticModelDefinition = string | LanguageModel),
so no change to eve itself was needed.
deepseekModel() uses @ai-sdk/openai's .chat(...) form explicitly, not the
callable-provider shorthand: the shorthand defaults to the Responses API
(POSTs to /responses), which DeepSeek's OpenAI-compatible endpoint does
not implement -- found empirically via the unit test's provider assertion.
`name: "deepseek"` overrides the default `openai` provider id.
selectedModel()/ModelSelection are kept (not removed as the task's draft
code suggested) because apps/agent/agent/subagents/agent_builder/agent.ts
and test/model.integration.spec.ts still depend on them for the per-run
custom-agent-builder model picker -- out of scope for this task, which
only swaps the main agent's static model. Its db import is now lazy so
importing this module (for deepseekModel(), from the new unit test)
doesn't eagerly require DATABASE_URL/TEST_DATABASE_URL.
The new unit test lives in apps/agent/test/ (not co-located next to
model.ts as first drafted) to match this app's existing convention --
every other bun:test spec lives there, specifically because apps/agent's
tsconfig include glob only covers agent/**, which has no bun:test types
and fails tsc --noEmit otherwise.
Dockerfile mirrors apps/api's and apps/app's (Tasks 3-4): DATABASE_URL
placeholder for @crm/db's postinstall prisma generate, multi-stage
bun build via turbo. Runtime CMD is `cd apps/agent && bun run start`,
not `bun apps/agent/scripts/start.ts` directly -- start.ts spawns the
`eve` CLI via child_process.spawn, which resolves against $PATH, and the
image's system PATH does not include node_modules/.bin (only `bun run`
augments it) -- confirmed empirically ("Executable not found in $PATH:
eve" with the direct form, despite the binary existing on disk).
No docker.io/docker CLI added to the runtime image: eve's sandbox backend
selection (selectDefaultSandbox in its own source) only tries Docker if
isDockerDaemonAvailableSync() is true, and falls back to microsandbox
otherwise. With no docker socket mounted (that lands in Task 6), the
container initializes its sandbox template and starts cleanly without
the CLI -- confirmed by running it standalone.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014caYm8q32Dzgm2TiTeKK8G
crm-api (3.65GB) and crm-agent (4.56GB) runtime images copied the FULL monorepo node_modules from the build stage (dev+prod deps for every workspace at once) -- repeatedly exhausted vigiep1's 72GB disk across rebuild cycles in production (29/08/2026). Both Dockerfiles now reinstall with 'bun install --production' in a dedicated stage before the runtime COPY, after removing the full node_modules tree. apps/agent/package.json: moved microsandbox and just-bash from devDependencies to dependencies -- eve's sandbox backend needs at least one importable at runtime even though docker.sock (the preferred backend in this deployment) is mounted; a --production install would otherwise have silently dropped them (carried-forward minor finding from Task 5's review, now directly relevant).
bun install --production re-triggers every workspace's postinstall hook
on a fresh install, including @crm/db's ('prisma generate') -- but prisma
(the CLI) is a devDependency, excluded by --production, so the hook fails
with exit 127. Unnecessary anyway: the Prisma Client was already
generated during the earlier 'build' stage and persists on disk
(packages/db/src/generated/), untouched by the node_modules rm+reinstall.
--ignore-scripts skips re-running it.
…endencies crm-agent crash-looped after the --production slim-down: '[TSCONFIG_ERROR] Failed to load tsconfig @crm/typescript-config/base.json: Tsconfig not found' -- eve's 'start' re-bundles the authored agent module on every boot (not just 'eve build' ahead of time), and needs tsconfig resolution (and presumably the typescript toolchain) present at runtime, not just at build/dev time. Both were devDependencies, excluded by --production.
… email/meeting NOTE_TYPES fed the "Notes" tab's filter clause, but included EMAIL and MEETING alongside NOTE/CALL -- both of which already have their own dedicated tab. Effect: every synced email and every synced meeting also showed up under Notes, which its own empty-state copy describes as "what you write down for the next person to read" -- i.e. manual entries only. Found investigating a report that "all mail lands in the notes channel" after the contact-history backfill made Notes tab traffic visible for the first time (previously the tab was empty because the live sync had never stored anything). NOTE_TYPES narrowed to [NOTE, CALL] -- the two types with no dedicated tab of their own.
fix(activities): Notes tab was showing every synced email and meeting
…hesis Requested after the deployed bulk-mail filter (gmail-message-parser.ts) didn't catch every Club'IT broadcast for Damien Vignault -- some slip under the recipient-count/List-Unsubscribe heuristics. Reps need a manual escape hatch for whatever the automated filter misses. EmailThread gains excludedAt (soft, not a delete -- the row and its rfcMessageId/rootMessageId dedup keys stay in place so the live sync never re-imports an excluded thread). ActivitiesService.timeline()/ timelineCounts() and ContactsService.relationship() now filter it out everywhere the synthesis reads from EmailThread/Activity. New tRPC mutations activities.excludeEmail/restoreEmail (reversible). Known gap, not covered by a dedicated test: ActivityStampService's lastActivityAt is not recomputed on exclude -- if the excluded thread was the most recent activity, lastActivityAt won't fall back to the next one until something else touches it. Acceptable for v1 (matches this session's "backend only, no UI yet" scoping), flagged for anyone picking this up next.
feat(activities): allow excluding an email thread from the CRM synthesis
DEC-C-CRM-10 (Franck, 2026-09-03): inbound direction of F.24 (VigieProcure's CRM gateway) activated. Branches into the same point that already queues agent-worker events (withCrmEvents) -- best-effort, HMAC-SHA256 signed on the raw body, same "no secret = no bridge" rule as the existing agent bridge. New vigieprocure-bridge.ts, VIGIEPROCURE_WEBHOOK_URL/_SECRET env vars (both optional, unset = no-op). createEventTask now returns the AgentTask id, used as a stable event_id for VigieProcure-side deduplication. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GLAb4MA3VwGs6CKRhxtsmD
Corrige deux lignes trop longues detectees par le hook pre-push (bun run lint). Aucun changement de comportement. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GLAb4MA3VwGs6CKRhxtsmD
Notify VigieProcure on CRM deal lifecycle events (DEC-C-CRM-10)
bunx biome check --write . after biome.jsonc's formatter/organizeImports rules. All 12 files are pure line-wrap/reformatting (long object literals, type unions, function signatures split across lines) -- verified diff-by-diff, no logic or values changed. This was the actual scope of the pre-push lint gate failure blocking feat/company-siren-column. The other ~790 files git status flagged were a false positive from local core.autocrlf=true (Windows checkout) vs this repo's LF blobs -- confirmed via `git diff --stat` (empty) and `git ls-files --eol` (i/lf w/lf, no divergence). No commit needed for those; see follow-up note on core.autocrlf / .gitattributes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LtJccFqpSuP8tQ3Pwg1dYH
…e.failed `no-unknown-parameters` allows an explicit `unknown` parameter only when named `cause` -- the convention every other error-normalizing method in this repo already follows (companies.service.ts, contacts.service.ts, deals.service.ts, fields.ts, main.ts, settings.service.ts all name it `cause`). This was the sole outlier, still named `error`. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LtJccFqpSuP8tQ3Pwg1dYH
…idening
`no-known-value-widening` -- `return { calls }` already carries evidence
for `{ calls: URL[] }`; the explicit anonymous return-type annotation
discarded it for no benefit (callers destructure `{ calls }` either way).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LtJccFqpSuP8tQ3Pwg1dYH
`no-runtime-typeof` -- bun:test ships a dedicated matcher for exactly this assertion, same intent without a bare typeof check. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LtJccFqpSuP8tQ3Pwg1dYH
…mport no-known-value-widening: gmailCaptured/calendarCaptured were annotated with anonymous object-literal types instead of the named contract already defined by the stub functions that mutate them (GmailStubOptions["captured"], calendarClientStub's own parameter type via Parameters<>) -- satisfies alone doesn't work here since these objects start empty and get filled by the stub's side effect. Also drops MailboxSyncModel (aliased MailboxSync), imported but never referenced -- preexisting on release, caught by biome's noUnusedImports while touching this file. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LtJccFqpSuP8tQ3Pwg1dYH
chore: fix lint debt (formatting + anti-slop retyping)
…alue no-unsafe-dictionary-type -- payload and the logger.debug() parameter were Record<string, unknown>, an unsafe unknown escape hatch. The repo already has a concrete owner type for this exact case: Prisma.InputJsonValue /InputJsonObject, used the same way for outbound JSON payloads elsewhere in apps/api/src/agent/ (agent-trigger.service.ts). No behavior change -- both are still structural JSON objects at runtime. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LtJccFqpSuP8tQ3Pwg1dYH
…e-anti-slop fix(anti-slop): type VigieProcureEvent.payload with Prisma.InputJsonValue
Rapproche la fiche CRM du referentiel SIRENE cote VigieProcure (GET /api/v1/companies/resolve, api_v2). Nullable -- la plupart des fiches n'ont pas encore ete resolues, et les comptes non francais n'en auront jamais. Meme patron que le champ `domain` deja en place : CHAR(9) format SIRENE standard, contrainte unique scopee sur les fiches actives (`archivedAt IS NULL`) pour permettre la reutilisation d'un SIREN apres archivage. Aucun seed/backfill : la migration ajoute la colonne vide, ne resout pas retroactivement les fiches existantes. Cf. plan/CR reports/2026-09/CR/CR-RESOLUTION-SIREN-CRM-20260904.md (repo vigieprocure) pour le contexte complet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LtJccFqpSuP8tQ3Pwg1dYH
ALTER TABLE company ADD COLUMN siren CHAR(9) + index unique partiel sur (siren) WHERE archivedAt IS NULL. Meme forme que la migration 20260820161500_archive_scoped_uniqueness (patron domain) deja en prod. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LtJccFqpSuP8tQ3Pwg1dYH
feat(db): add siren column to Company, unique scoped on active records
Ajoute deux procedures tRPC (companies.resolveSiren, companies.setSiren) qui appellent GET /api/v1/companies/resolve cote api_v2 avec un JWT de service (VIGIEPROCURE_API_JWT/VIGIEPROCURE_API_URL, meme doctrine que VIGIEPROCURE_WEBHOOK_URL/_SECRET -- absent = fonctionnalite degradee, jamais un appel non authentifie). Sur la fiche company : affiche le SIREN existant, ou propose de le resoudre sur clic explicite. Un seul candidat "exact" ecrit automatiquement (exception ciblee et reversible, validee par Franck -- jamais depuis un effet au chargement ni un backfill de masse). Sinon, liste les candidats pour choix manuel. Conflit d'unicite (P2002 sur Company.siren) traduit en erreur utilisateur nommant la fiche en conflit, jamais un 500 nu. Inclut la regeneration de apps/api/src/generated/server.ts, qui corrige au passage une dette preexistante (excludeEmail/restoreEmail declares dans activities.router.ts depuis le commit 044e350 mais jamais regeneres). A provisionner separement (hors perimetre de ce chantier) : VIGIEPROCURE_API_URL, VIGIEPROCURE_API_JWT sur crm-api en production. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LtJccFqpSuP8tQ3Pwg1dYH
- companySetSirenInput.siren: .length(9) laissait passer 9 caracteres non numeriques alors que le message annoncait "9 digits" -- .regex le fait respecter reellement. - companySetSirenOutput (conflict): conflictingCompanyId/Name n'etaient consommes nulle part cote frontend (seul `reason` est lu), et le fallback `conflicting?.id ?? ""` etait un `z.string()` non-nullable rempli d'une chaine vide dans un cas quasi mort (le findFirst est scope par la meme contrainte unique partielle que le P2002 qui declenche ce chemin). Retire les deux champs plutot que de les rendre nullable pour un consommateur qui n'existe pas. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LtJccFqpSuP8tQ3Pwg1dYH
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LtJccFqpSuP8tQ3Pwg1dYH
feat(companies): resolve and set SIREN via VigieProcure
Déplace le build Docker des 3 services (api/app/agent) de vigiep1 vers GitHub Actions. Sur vigiep1, bun install (1771+ paquets) prenait 36-41min par service faute de cache incrémental (Dockerfile réinstalle à froid volontairement, cf. incident disque plein du 29/08) et de disque I/O lent sur /mnt/hermes-extra -- un rebuild complet des 3 services dépassait 2h. Le cache GitHub Actions (type=gha) devrait ramener ça à quelques dizaines de secondes sur les runs suivants, sur des runners dédiés (pas de contention RAM/IO avec dagster/n8n/ vigieproc-api qui tournent sur le même host). vigiep1 passera de `docker compose build` à `docker compose pull` -- changement du docker-compose.yml de production (non versionné dans ce repo) à faire séparément, avec provisionnement d'un PAT read:packages pour l'authentification à ghcr.io (registre privé). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LtJccFqpSuP8tQ3Pwg1dYH
feat: release release
…uete Le tracker JS servi par /t/[site] embarquait une URL d'ingestion cassee (https://0.0.0.0:3000/api/t/e au lieu de https://crm.vigieproc.fr/api/t/e), donc aucun evenement de tracking n'atteignait jamais crm-api en production. `new URL(request.url).origin` reflete le socket brut vu par le serveur Next.js sous `next start`, pas le Host public transmis par nginx -- confirme en forcant Host/X-Forwarded-* directement contre le conteneur sans effet. APP_URL existe deja dans l'environnement du conteneur (compose vigiep1) et sert deja le meme role (session cote serveur, allowedDevOrigins) mais n'avait pas d'export dans lib/env.ts, contrairement a API_URL. Ajoute cet export (lecture directe, sans indirection NEXT_PUBLIC_ -- ce fichier est un route handler server-side, jamais expose au bundle client) et l'utilise pour construire l'URL d'ingestion du tracker. Diagnostic : crm-trycompai-orchestrator + devops-orchestrator (nginx ecarte par test direct), confirme independamment ici par lecture du Dockerfile/compose/build-images.yml. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P6yNGEKWRMLZrVyjCrD61G
fix(tracking): utilise APP_URL au lieu de l'origine derivee de la requete
…ain--components--crm chore(main): release 1.16.0
|
@franckh-stack is attempting to deploy a commit to the Comp AI - PoC Team on Vercel. A member of the Team first needs to authorize it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary by cubic
Bundles v1.16.0: containerized deployment with CI image builds, automatic Gmail/Calendar history backfill on contact creation, and two new VigieProcure integrations.
Deployment
api,app, andagentwith production-only dependency installs.emailThread.excludedAtandcompany.siren.Sync, VigieProcure, and fixes
VIGIEPROCURE_WEBHOOK_URL/_SECRETare set.APP_URLinstead of the request-derived origin.Written for commit d8eb2b0. Summary will update on new commits.