thinkwork-cli 0.12.16 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/dist/cli.js +517 -10403
  2. package/dist/commands/enterprise/templates/deploy-repo/docs/runbook.md +3 -8
  3. package/dist/commands/enterprise/templates/deploy-repo/terraform/main.tf +1 -184
  4. package/dist/drizzle/0050_brain_v0_entity_subtype.sql +2 -2
  5. package/dist/drizzle/0051_brain_v0_tenant_entity_pages.sql +6 -6
  6. package/dist/drizzle/0052_brain_v0_external_refs.sql +2 -2
  7. package/dist/drizzle/0053_brain_v0_unresolved_mentions_subtype.sql +2 -2
  8. package/dist/drizzle/0145_knowledge_graph_thread_ingest.sql +14 -14
  9. package/dist/drizzle/0146_knowledge_graph_source_scope.sql +1 -1
  10. package/dist/drizzle/0157_knowledge_graph_observations_source.sql +2 -2
  11. package/dist/drizzle/0158_wiki_tenant_scope.sql +1 -1
  12. package/dist/drizzle/0165_plugin_premium_entitlements.sql +2 -2
  13. package/dist/drizzle/{0166_company_brain_substrate_contract.sql → 0166_brain_substrate_contract.sql} +9 -9
  14. package/dist/drizzle/{0167_company_brain_artifact_manifest_runtime.sql → 0167_brain_artifact_manifest_runtime.sql} +5 -5
  15. package/dist/drizzle/0168_eval_skill_gate.sql +1 -1
  16. package/dist/drizzle/0183_okf_artifact_manifests.sql +1 -1
  17. package/dist/drizzle/0200_eval_results_execution_tier.sql +40 -0
  18. package/dist/drizzle/0201_brain_dream_runs.sql +72 -0
  19. package/dist/drizzle/0202_backfill_thread_participants_from_senders.sql +110 -0
  20. package/dist/drizzle/0203_threads_mode_override.sql +61 -0
  21. package/dist/drizzle/0204_thread_turns_triggering_message_id.sql +48 -0
  22. package/dist/drizzle/0205_routines_git_python.sql +185 -0
  23. package/dist/drizzle/0206_agent_loop_versions_routine_actions.sql +40 -0
  24. package/dist/drizzle/0207_tenant_credentials_github_repo_kind.sql +52 -0
  25. package/dist/drizzle/0208_knowledge_graph_neutral_identifiers.sql +253 -0
  26. package/dist/drizzle/0209_living_artifacts_schema.sql +109 -0
  27. package/dist/drizzle/0210_think137_automations_additive_schema.sql +113 -0
  28. package/dist/drizzle/0211_backfill_agent_loop_versions_target_spec.sql +120 -0
  29. package/dist/drizzle/0212_fold_webhooks_into_automations.sql +312 -0
  30. package/dist/drizzle/0213_agent_loop_versions_judge_evidence_nullable.sql +63 -0
  31. package/dist/drizzle/0214_think137_drop_judge_evidence_roi.sql +99 -0
  32. package/dist/drizzle/0215_agent_loop_versions_goal_worker_policy_nullable.sql +70 -0
  33. package/dist/drizzle/0216_drop_agent_loop_versions_goal_worker_policy.sql +81 -0
  34. package/dist/drizzle/0217_agents_capability_folder_dispatch.sql +12 -0
  35. package/dist/drizzle/0218_document_plates.sql +30 -0
  36. package/dist/drizzle/0219_document_section_waivers.sql +29 -0
  37. package/dist/drizzle/0220_artifacts_created_by_user.sql +21 -0
  38. package/dist/drizzle/0221_artifact_shares.sql +29 -0
  39. package/dist/drizzle/0221_workflow_interpreter_thin_slice.sql +71 -0
  40. package/dist/drizzle/0222_compliance_drainer_select_audit_events.sql +21 -0
  41. package/dist/drizzle/0223_document_conformance_reports.sql +38 -0
  42. package/dist/drizzle/0224_artifact_refresh_state.sql +11 -0
  43. package/dist/drizzle/0225_think216_agent_loop_convergence.sql +21 -0
  44. package/dist/drizzle/0226_drop_n8n_agent_step_runs.sql +6 -0
  45. package/dist/drizzle/0227_analyst_reader_role.sql +1055 -0
  46. package/dist/drizzle/0228_tenant_credentials_rds_iam_kind.sql +62 -0
  47. package/dist/drizzle/0229_analyst_reader_rds_iam_grant.sql +79 -0
  48. package/dist/drizzle/0230_analyst_rls.sql +1613 -0
  49. package/dist/drizzle/0231_cost_events_cache_write_enforcement_exempt.sql +11 -0
  50. package/dist/plugins/catalog/package.json +0 -1
  51. package/dist/plugins/catalog/src/__tests__/build-catalog.test.ts +0 -1
  52. package/dist/plugins/catalog/src/__tests__/catalog.test.ts +16 -8
  53. package/dist/plugins/catalog/src/__tests__/contracts.test.ts +56 -0
  54. package/dist/plugins/catalog/src/__tests__/plugin-package.test.ts +0 -2
  55. package/dist/plugins/catalog/src/__tests__/plugin-registry.test.ts +0 -5
  56. package/dist/plugins/catalog/src/contracts.ts +112 -0
  57. package/dist/plugins/catalog/src/registry/generated-first-party.ts +0 -6
  58. package/dist/plugins/n8n/README.md +4 -36
  59. package/dist/plugins/n8n/src/deployment/managed-app.ts +0 -17
  60. package/dist/plugins/n8n/src/manifest.ts +0 -5
  61. package/dist/plugins/n8n/terraform/n8n/main.tf +0 -26
  62. package/dist/plugins/n8n/terraform/n8n/outputs.tf +0 -5
  63. package/dist/plugins/n8n/terraform/n8n/variables.tf +0 -6
  64. package/dist/plugins/n8n/test/manifest.test.ts +0 -1
  65. package/dist/plugins/twenty/package.json +5 -0
  66. package/dist/plugins/twenty/scripts/lib/__tests__/load-records.test.ts +232 -0
  67. package/dist/plugins/twenty/scripts/lib/__tests__/mappers.test.ts +489 -0
  68. package/dist/plugins/twenty/scripts/lib/__tests__/members-ensure.test.ts +199 -0
  69. package/dist/plugins/twenty/scripts/lib/__tests__/provision-members-db.test.ts +43 -0
  70. package/dist/plugins/twenty/scripts/lib/__tests__/schema-ensure.test.ts +230 -0
  71. package/dist/plugins/twenty/scripts/lib/__tests__/twenty-client.test.ts +207 -0
  72. package/dist/plugins/twenty/scripts/lib/lastmile-reader.ts +282 -0
  73. package/dist/plugins/twenty/scripts/lib/load-attachments.ts +220 -0
  74. package/dist/plugins/twenty/scripts/lib/load-records.ts +680 -0
  75. package/dist/plugins/twenty/scripts/lib/mappers.ts +559 -0
  76. package/dist/plugins/twenty/scripts/lib/members-ensure.ts +381 -0
  77. package/dist/plugins/twenty/scripts/lib/provision-members-db.ts +191 -0
  78. package/dist/plugins/twenty/scripts/lib/schema-ensure.ts +425 -0
  79. package/dist/plugins/twenty/scripts/lib/twenty-client.ts +246 -0
  80. package/dist/plugins/twenty/scripts/migrate-lastmile.ts +610 -0
  81. package/dist/plugins/twenty/scripts/provision-twenty-members.ts +214 -0
  82. package/dist/plugins/twenty/src/manifest.ts +99 -0
  83. package/dist/plugins/twenty/terraform/twenty/README.md +1 -1
  84. package/dist/plugins/twenty/test/manifest.test.ts +12 -2
  85. package/dist/terraform/examples/greenfield/main.tf +90 -380
  86. package/dist/terraform/examples/greenfield/terraform.tfvars.example +0 -1
  87. package/dist/terraform/modules/app/agentcore-pi/README.md +1 -3
  88. package/dist/terraform/modules/app/agentcore-pi/main.tf +29 -38
  89. package/dist/terraform/modules/app/agentcore-pi/variables.tf +23 -15
  90. package/dist/terraform/modules/app/agentcore-runtime/main.tf +3 -3
  91. package/dist/terraform/modules/app/customer-domain/main.tf +16 -2
  92. package/dist/terraform/modules/app/deployment-control-plane/main.tf +5 -0
  93. package/dist/terraform/modules/app/deployment-control-plane/runner.py +85 -207
  94. package/dist/terraform/modules/app/deployment-control-plane/test_runner_bundle.py +56 -2
  95. package/dist/terraform/modules/app/hindsight-memory/main.tf +52 -4
  96. package/dist/terraform/modules/app/lambda-api/eval-fanout.tf +7 -0
  97. package/dist/terraform/modules/app/lambda-api/handlers.tf +493 -89
  98. package/dist/terraform/modules/app/lambda-api/iam-grouped.tf +215 -96
  99. package/dist/terraform/modules/app/lambda-api/main.tf +24 -5
  100. package/dist/terraform/modules/app/lambda-api/outputs.tf +19 -11
  101. package/dist/terraform/modules/app/lambda-api/variables.tf +94 -67
  102. package/dist/terraform/modules/app/ses-email/main.tf +14 -2
  103. package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/main.tf +493 -0
  104. package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/outputs.tf +34 -0
  105. package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/variables.tf +32 -0
  106. package/dist/terraform/modules/data/aurora-postgres/main.tf +55 -0
  107. package/dist/terraform/modules/data/aurora-postgres/outputs.tf +15 -0
  108. package/dist/terraform/modules/data/s3-buckets/main.tf +26 -0
  109. package/dist/terraform/modules/thinkwork/README.md +0 -1
  110. package/dist/terraform/modules/thinkwork/main.tf +167 -446
  111. package/dist/terraform/modules/thinkwork/outputs.tf +2 -142
  112. package/dist/terraform/modules/thinkwork/variables.tf +55 -309
  113. package/dist/terraform/schema.graphql +4 -0
  114. package/dist/workspace-defaults/files/AGENTS.md +45 -31
  115. package/dist/workspace-defaults/files/CONTEXT.md +48 -4
  116. package/dist/workspace-defaults/files/GUARDRAILS.md +18 -0
  117. package/dist/workspace-defaults/files/MEMORY_GUIDE.md +32 -4
  118. package/dist/workspace-defaults/files/SPACE.md +34 -19
  119. package/dist/workspace-defaults/files/TOOLS.md +30 -0
  120. package/dist/workspace-defaults/files/skills/document-composer/SKILL.md +154 -0
  121. package/dist/workspace-defaults/files/skills/document-composer/references/authoring-rules.md +101 -0
  122. package/package.json +1 -1
  123. package/dist/plugins/company-brain/README.md +0 -37
  124. package/dist/plugins/company-brain/package.json +0 -33
  125. package/dist/plugins/company-brain/runtime/cognee/Dockerfile +0 -11
  126. package/dist/plugins/company-brain/smoke/cognee-managed-app-smoke.mjs +0 -458
  127. package/dist/plugins/company-brain/smoke/cognee-memory-cutover-smoke.mjs +0 -539
  128. package/dist/plugins/company-brain/smoke/company-brain-context-engine-smoke.mjs +0 -312
  129. package/dist/plugins/company-brain/smoke/company-brain-operations-smoke.mjs +0 -449
  130. package/dist/plugins/company-brain/smoke/company-brain-plugin-smoke.mjs +0 -655
  131. package/dist/plugins/company-brain/smoke/hindsight-memory-isolation-smoke.mjs +0 -474
  132. package/dist/plugins/company-brain/src/api/cognee-client.ts +0 -896
  133. package/dist/plugins/company-brain/src/api/cognee-cluster-identity.ts +0 -49
  134. package/dist/plugins/company-brain/src/api/cognee-memory-scope.ts +0 -84
  135. package/dist/plugins/company-brain/src/api/context-engine-provider.ts +0 -888
  136. package/dist/plugins/company-brain/src/api/migration.ts +0 -665
  137. package/dist/plugins/company-brain/src/deployment/cognee-managed-app.ts +0 -396
  138. package/dist/plugins/company-brain/src/index.ts +0 -62
  139. package/dist/plugins/company-brain/src/manifest.ts +0 -112
  140. package/dist/plugins/company-brain/terraform/cognee/README.md +0 -263
  141. package/dist/plugins/company-brain/terraform/cognee/main.tf +0 -766
  142. package/dist/plugins/company-brain/terraform/cognee/outputs.tf +0 -144
  143. package/dist/plugins/company-brain/terraform/cognee/variables.tf +0 -402
  144. package/dist/plugins/company-brain/test/api/cognee-client.test.ts +0 -562
  145. package/dist/plugins/company-brain/test/api/cognee-memory-scope.test.ts +0 -74
  146. package/dist/plugins/company-brain/test/api/context-engine-provider.test.ts +0 -447
  147. package/dist/plugins/company-brain/test/api/migration.test.ts +0 -465
  148. package/dist/plugins/company-brain/test/manifest.test.ts +0 -95
  149. package/dist/plugins/company-brain/tsconfig.json +0 -8
@@ -0,0 +1,214 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * Provision TEI's LastMile sales reps as Twenty workspace members.
4
+ *
5
+ * TEI's Twenty does not serve the auth GraphQL schema (see lib/provision-
6
+ * members-db.ts for the evidence), so members are written directly to Twenty's
7
+ * Postgres. INSERT-only, idempotent by email, one transaction per rep.
8
+ *
9
+ * Environment:
10
+ * TWENTY_DATABASE_URL Twenty's Postgres (secretsmanager thinkwork/tei-e2e/twenty/db-url)
11
+ * LASTMILE_DATABASE_URL LastMile dispatch Postgres (read-only)
12
+ * TWENTY_REP_PASSWORD Shared validation-window password. ROTATE AT CUTOVER.
13
+ * TWENTY_REP_EMAIL_DOMAIN Fallback email domain (default texasenterprises.com)
14
+ * TWENTY_WORKSPACE_ID Optional; resolved from core.workspace when single-workspace
15
+ *
16
+ * Usage:
17
+ * npx tsx scripts/provision-twenty-members.ts # dry-run: who would be created
18
+ * npx tsx scripts/provision-twenty-members.ts --apply # create them
19
+ * npx tsx scripts/provision-twenty-members.ts --apply --only me@x.com # one rep, for proof
20
+ *
21
+ * After provisioning, re-run migrate-lastmile.ts --apply so every company and
22
+ * opportunity picks up its owner.
23
+ */
24
+
25
+ import process from "node:process";
26
+
27
+ import pg from "pg";
28
+
29
+ import { createLastmileReader } from "./lib/lastmile-reader";
30
+ import { deriveRepEmail } from "./lib/mappers";
31
+ import {
32
+ provisionMembers,
33
+ type ProvisionRep,
34
+ } from "./lib/provision-members-db";
35
+
36
+ function requireEnv(name: string): string {
37
+ const value = process.env[name];
38
+ if (!value) throw new Error(`Missing required environment variable: ${name}`);
39
+ return value;
40
+ }
41
+
42
+ function log(message: string): void {
43
+ process.stderr.write(`${message}\n`);
44
+ }
45
+
46
+ interface WorkspaceContext {
47
+ workspaceId: string;
48
+ roleId: string;
49
+ applicationId: string;
50
+ workspaceSchema: string;
51
+ }
52
+
53
+ /** Read the workspace's own wiring rather than hardcode ids. */
54
+ async function readWorkspaceContext(
55
+ databaseUrl: string,
56
+ workspaceIdOverride: string | undefined,
57
+ ): Promise<WorkspaceContext> {
58
+ const pool = new pg.Pool({ connectionString: databaseUrl, max: 1 });
59
+ try {
60
+ // core.workspace has no schemaName column on this version; the data
61
+ // schema is resolved from information_schema below.
62
+ const workspaces = await pool.query<{
63
+ id: string;
64
+ defaultRoleId: string | null;
65
+ }>(
66
+ `select id, "defaultRoleId" from core.workspace where "deletedAt" is null`,
67
+ );
68
+ if (workspaces.rows.length === 0) throw new Error("No workspace found.");
69
+ const workspace = workspaceIdOverride
70
+ ? workspaces.rows.find((row) => row.id === workspaceIdOverride)
71
+ : workspaces.rows[0];
72
+ if (!workspace)
73
+ throw new Error(`Workspace ${workspaceIdOverride} not found.`);
74
+ if (workspaces.rows.length > 1 && !workspaceIdOverride) {
75
+ throw new Error("Multiple workspaces — set TWENTY_WORKSPACE_ID.");
76
+ }
77
+ if (!workspace.defaultRoleId) {
78
+ throw new Error(
79
+ "Workspace has no defaultRoleId; refusing to guess a role.",
80
+ );
81
+ }
82
+
83
+ // Reuse the applicationId the workspace's existing roleTargets carry.
84
+ const roleTarget = await pool.query<{ applicationId: string }>(
85
+ `select "applicationId" from core."roleTarget" where "workspaceId" = $1 limit 1`,
86
+ [workspace.id],
87
+ );
88
+ if (roleTarget.rows.length === 0) {
89
+ throw new Error("No existing roleTarget to copy applicationId from.");
90
+ }
91
+
92
+ const schemas = await pool.query<{ schema_name: string }>(
93
+ `select schema_name from information_schema.schemata where schema_name like 'workspace\\_%'`,
94
+ );
95
+ if (schemas.rows.length !== 1) {
96
+ throw new Error(
97
+ `Expected exactly one workspace_* data schema, found ${schemas.rows.length}.`,
98
+ );
99
+ }
100
+ const schemaName = schemas.rows[0].schema_name;
101
+
102
+ return {
103
+ workspaceId: workspace.id,
104
+ roleId: workspace.defaultRoleId,
105
+ applicationId: roleTarget.rows[0].applicationId,
106
+ workspaceSchema: schemaName,
107
+ };
108
+ } finally {
109
+ await pool.end();
110
+ }
111
+ }
112
+
113
+ async function main(): Promise<void> {
114
+ const argv = process.argv.slice(2);
115
+ let apply = false;
116
+ let only: string | null = null;
117
+ for (let index = 0; index < argv.length; index += 1) {
118
+ const arg = argv[index];
119
+ if (arg === "--apply") apply = true;
120
+ else if (arg === "--dry-run") apply = false;
121
+ else if (arg === "--only") {
122
+ only = argv[++index] ?? null;
123
+ if (!only) throw new Error("--only requires an email");
124
+ } else throw new Error(`Unknown argument: ${arg}`);
125
+ }
126
+ const dryRun = !apply;
127
+
128
+ const twentyDbUrl = requireEnv("TWENTY_DATABASE_URL");
129
+ const lastmileUrl = requireEnv("LASTMILE_DATABASE_URL");
130
+ const password = requireEnv("TWENTY_REP_PASSWORD");
131
+ const emailDomain =
132
+ process.env.TWENTY_REP_EMAIL_DOMAIN ?? "texasenterprises.com";
133
+
134
+ const context = await readWorkspaceContext(
135
+ twentyDbUrl,
136
+ process.env.TWENTY_WORKSPACE_ID,
137
+ );
138
+ log(
139
+ `workspace ${context.workspaceId} schema ${context.workspaceSchema} role ${context.roleId}`,
140
+ );
141
+
142
+ const reader = createLastmileReader(lastmileUrl);
143
+ let reps: ProvisionRep[];
144
+ try {
145
+ const sourceReps = await reader.readReps();
146
+ const seen = new Set<string>();
147
+ reps = [];
148
+ let derived = 0;
149
+ for (const rep of sourceReps) {
150
+ if (rep.archived) continue;
151
+ const email =
152
+ rep.email ?? deriveRepEmail(rep.firstName, rep.lastName, emailDomain);
153
+ if (!email) continue; // house/intercompany/placeholder rows get no login
154
+ if (!rep.email) derived += 1;
155
+ // Two rep rows can share an email (a house row reusing a real address,
156
+ // or one person duplicated). Email is the login identity: one user.
157
+ if (seen.has(email)) continue;
158
+ seen.add(email);
159
+ reps.push({
160
+ repId: rep.id,
161
+ email,
162
+ firstName: rep.firstName,
163
+ lastName: rep.lastName,
164
+ });
165
+ }
166
+ log(
167
+ `reps: ${reps.length} provisionable (${derived} with derived @${emailDomain} emails), ` +
168
+ `${sourceReps.filter((rep) => !rep.archived).length - reps.length} unprovisionable`,
169
+ );
170
+ } finally {
171
+ await reader.close();
172
+ }
173
+
174
+ if (only) {
175
+ const target = only.toLowerCase();
176
+ reps = reps.filter((rep) => rep.email.toLowerCase() === target);
177
+ if (reps.length === 0) throw new Error(`--only ${only} matched no rep`);
178
+ }
179
+
180
+ const result = await provisionMembers({
181
+ databaseUrl: twentyDbUrl,
182
+ workspaceId: context.workspaceId,
183
+ roleId: context.roleId,
184
+ applicationId: context.applicationId,
185
+ workspaceSchema: context.workspaceSchema,
186
+ password,
187
+ reps,
188
+ dryRun,
189
+ log,
190
+ });
191
+
192
+ process.stdout.write(
193
+ `${JSON.stringify(
194
+ {
195
+ mode: dryRun ? "dry-run" : "apply",
196
+ workspaceId: context.workspaceId,
197
+ created: result.created,
198
+ existing: result.existing,
199
+ failed: result.failed,
200
+ rows: result.rows.slice(0, 200),
201
+ },
202
+ null,
203
+ 2,
204
+ )}\n`,
205
+ );
206
+ if (result.failed > 0) process.exitCode = 2;
207
+ }
208
+
209
+ main().catch((error) => {
210
+ process.stderr.write(
211
+ `${error instanceof Error ? error.message : String(error)}\n`,
212
+ );
213
+ process.exit(1);
214
+ });
@@ -255,5 +255,104 @@ export const twentyManifest = {
255
255
  },
256
256
  ],
257
257
  },
258
+ {
259
+ version: "0.3.1",
260
+ requiredOauthScopes: [],
261
+ components: [
262
+ {
263
+ type: "mcp-server",
264
+ key: "crm",
265
+ displayName: "Twenty CRM",
266
+ description:
267
+ "CRM records (people, companies, opportunities, notes) on the tenant's deployed Twenty instance.",
268
+ endpointFrom: {
269
+ managedApp: "twenty",
270
+ configKey: "publicUrl",
271
+ path: "/mcp",
272
+ },
273
+ auth: { mode: "oauth-per-instance" },
274
+ recordLinkHints: {
275
+ schemaVersion: 1,
276
+ source: "plugin-manifest",
277
+ routes: [
278
+ {
279
+ objectType: "opportunity",
280
+ routeTemplate: "/object/opportunity/{id}",
281
+ idFields: [
282
+ "id",
283
+ "opportunityId",
284
+ "record.id",
285
+ "opportunity.id",
286
+ ],
287
+ labelFields: [
288
+ "name",
289
+ "opportunityName",
290
+ "record.name",
291
+ "opportunity.name",
292
+ ],
293
+ },
294
+ ],
295
+ },
296
+ resultTransforms: [
297
+ {
298
+ type: "scaled-integer-to-decimal",
299
+ sourceField: "amountMicros",
300
+ targetField: "value",
301
+ scale: 6,
302
+ removeSource: true,
303
+ },
304
+ ],
305
+ toolNotes: [
306
+ "Twenty's MCP server exposes CRM object tools (people, companies, opportunities, tasks, notes); list tools first — the set varies by Twenty release.",
307
+ ],
308
+ },
309
+ {
310
+ type: "infrastructure",
311
+ key: "runtime",
312
+ managedAppKey: "twenty",
313
+ terraformInputs: {
314
+ imageUri: {
315
+ description:
316
+ "Twenty CRM container image URI pinned with @sha256.",
317
+ type: "string",
318
+ },
319
+ dbUrlSecretArn: {
320
+ description: "Secrets Manager ARN containing PG_DATABASE_URL.",
321
+ type: "string",
322
+ },
323
+ encryptionKeySecretArn: {
324
+ description: "Secrets Manager ARN containing ENCRYPTION_KEY.",
325
+ type: "string",
326
+ },
327
+ publicUrl: {
328
+ description: "Public HTTPS origin for Twenty CRM.",
329
+ type: "string",
330
+ },
331
+ certificateArn: {
332
+ description: "ACM certificate ARN for the public HTTPS listener.",
333
+ type: "string",
334
+ },
335
+ },
336
+ },
337
+ {
338
+ type: "ui-surface",
339
+ key: "client-engagement",
340
+ displayName: "Client Engagement",
341
+ intendedMount: "apps.main",
342
+ launch: {
343
+ schemaVersion: 1,
344
+ type: "app",
345
+ appKey: "twenty-client-engagement",
346
+ routeSegment: "client-engagement",
347
+ mount: "main-shell",
348
+ runtime: "trusted-bundled-react",
349
+ description:
350
+ "Account and opportunity engagement workspace for Twenty CRM records.",
351
+ icon: "layout-dashboard",
352
+ entitlementProductKey: "twenty-client-engagement",
353
+ },
354
+ },
355
+ ],
356
+ },
258
357
  ],
259
358
  };
@@ -1,7 +1,7 @@
1
1
  # Twenty CRM App Module
2
2
 
3
3
  This module provisions the optional Twenty CRM managed application substrate for
4
- ThinkWork. It follows the Cognee optional-app shape but uses a public HTTPS ALB,
4
+ ThinkWork. It follows the managed optional-app shape but uses a public HTTPS ALB,
5
5
  separate ECS services for the Twenty server and worker, a dedicated PostgreSQL
6
6
  database URL injected from Secrets Manager, EFS for local server storage, and
7
7
  ElastiCache for Valkey/Redis OSS.
@@ -108,6 +108,7 @@ describe("twenty plugin manifest", () => {
108
108
  "0.1.0",
109
109
  "0.2.0",
110
110
  "0.3.0",
111
+ "0.3.1",
111
112
  ]);
112
113
 
113
114
  const components = latestVersion(validated).components;
@@ -162,7 +163,7 @@ describe("twenty plugin manifest", () => {
162
163
  },
163
164
  ],
164
165
  });
165
- expect(latestVersion(validated).version).toBe("0.3.0");
166
+ expect(latestVersion(validated).version).toBe("0.3.1");
166
167
  expect(latestMcp.recordLinkHints).toEqual({
167
168
  schemaVersion: 1,
168
169
  source: "plugin-manifest",
@@ -180,6 +181,15 @@ describe("twenty plugin manifest", () => {
180
181
  },
181
182
  ],
182
183
  });
184
+ expect(latestMcp.resultTransforms).toEqual([
185
+ {
186
+ type: "scaled-integer-to-decimal",
187
+ sourceField: "amountMicros",
188
+ targetField: "value",
189
+ scale: 6,
190
+ removeSource: true,
191
+ },
192
+ ]);
183
193
  });
184
194
 
185
195
  it("publishes a launchable Client Engagement app surface in the latest version", () => {
@@ -457,7 +467,7 @@ describe("twenty plugin manifest", () => {
457
467
  validatedTwentyManifest.versions.map(
458
468
  (version) => version.requiredOauthScopes,
459
469
  ),
460
- ).toEqual([[], [], []]);
470
+ ).toEqual([[], [], [], []]);
461
471
  expect(() => validatePluginManifest(twentyManifest)).not.toThrow();
462
472
  });
463
473
  });