Conversation
- Replace `LINEA_AGENT` with `AgentFactory` for agent retrieval - Remove unused imports and adjust card components layout
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
This PR introduces the initial “Linea” product copilot foundations: a new integrations domain (OAuth + webhooks), expanded Linea services (skills, onboarding, jobs, admin APIs), and supporting UI/docs updates.
Changes:
- Added
core-integrationdomain with token encryption, OAuth/webhook ingestion, and provider clients (Linear/Slack/GitHub/Google). - Expanded Linea with workspace-specific prompts, skills injection, inbox thread creation/events, jobs scheduling, and admin GraphQL endpoints.
- Updated customer UI auth flow/pages and added extensive docs for integrations, architecture, and demos.
Reviewed changes
Copilot reviewed 91 out of 159 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| libs/core/linea/src/lib/services/index.ts | Re-exports new Linea services/factories for broader module usage. |
| libs/core/linea/src/lib/services/identity-linking-graphs.service.ts | Adds identity linking graph to connect GitHub/Linear/Slack accounts into memories. |
| libs/core/linea/src/lib/services/heartbeat-settings.service.ts | Adds heartbeat settings persistence via MemoryService. |
| libs/core/linea/src/lib/services/graphs.factory.ts | Enriches GitHub signal normalization and adds PR context extraction. |
| libs/core/linea/src/lib/services/agent.factory.ts | Adds workspace-specific agents, skills injection, and prompt composition. |
| libs/core/linea/src/lib/services/agent-prompt.service.ts | Introduces workspace prompt persistence via MemoryService. |
| libs/core/linea/src/lib/providers/models.provider.ts | Changes model configuration by removing explicit temperature setting. |
| libs/core/linea/src/lib/prompts/subagents.ts | Adds prompts for specialized subagents. |
| libs/core/linea/src/lib/prompts/index.ts | Refactors prompt exports into separate prompt modules. |
| libs/core/linea/src/lib/prompts/agent.ts | Defines new system prompt and helper to build workspace-aware prompts. |
| libs/core/linea/src/lib/linea.module.ts | Wires IntegrationModule, Bull queue, admin resolver, jobs, and new services. |
| libs/core/linea/src/lib/linea.facade.ts | Updates facade to use AgentFactory and adds inbox thread creation / graph config. |
| libs/core/linea/src/lib/linea.events.ts | Introduces Linea pub-sub event constants. |
| libs/core/linea/src/lib/linea-admin.resolver.ts | Adds GraphQL admin endpoints for jobs, memories, prompts, skills, settings, team graph. |
| libs/core/linea/src/lib/linea-admin.models.ts | Adds GraphQL types/inputs for Linea admin features. |
| libs/core/linea/src/lib/jobs/linea-jobs.types.ts | Defines job payload formats for heartbeat + scheduled tasks. |
| libs/core/linea/src/lib/jobs/linea-jobs.service.ts | Implements BullMQ job scheduling and listing. |
| libs/core/linea/src/lib/jobs/linea-jobs.constants.ts | Adds queue/job constants and heartbeat interval. |
| libs/core/linea/src/lib/assistant.service.ts | Enhances thread listing, adds inbox thread creation + pubsub emission, inbox status updates. |
| libs/core/linea/src/lib/assistant.controller.ts | Switches to workspace-aware DeepAgent usage and adds workspace prompt endpoints. |
| libs/core/linea/src/index.ts | Exposes additional Linea services and types to other packages/apps. |
| libs/core/integration/tsconfig.spec.json | Adds Jest/Node TS config for integration library tests. |
| libs/core/integration/tsconfig.lib.json | Adds library TS build config for integration package. |
| libs/core/integration/tsconfig.json | Adds base TS project references for integration package. |
| libs/core/integration/src/lib/slack.service.ts | Adds Slack Web API wrapper (channels/users/messages/post). |
| libs/core/integration/src/lib/slack-bolt.service.ts | Adds Slack Bolt receiver mounting and event forwarding into webhook service. |
| libs/core/integration/src/lib/linear.service.ts | Adds Linear SDK wrapper + webhook signature verification + token refresh. |
| libs/core/integration/src/lib/integration.webhook.service.ts | Adds webhook processing for Linear/Slack/GitHub and publishes events to event bus. |
| libs/core/integration/src/lib/integration.service.ts | Adds Integration CRUD, AES-GCM token encryption/decryption, refresh-on-expiry. |
| libs/core/integration/src/lib/integration.resolver.ts | Adds GraphQL queries/mutations for integration listing and deletion. |
| libs/core/integration/src/lib/integration.module.ts | Declares integration domain providers/controllers/resolver/facade. |
| libs/core/integration/src/lib/integration.models.ts | Adds GraphQL models/enums and OAuth input/state types. |
| libs/core/integration/src/lib/integration.facade.ts | Adds cross-domain integration accessor (tokens + active integrations). |
| libs/core/integration/src/lib/google.service.ts | Adds Google OAuth token refresh + Gmail/Calendar API helpers. |
| libs/core/integration/src/index.ts | Exports integration module/services/models. |
| libs/core/integration/project.json | Registers Nx project for the integration domain library. |
| libs/core/integration/jest.config.cts | Adds Jest config for the integration library. |
| libs/core/integration/eslint.config.mjs | Adds ESLint config passthrough for integration library. |
| libs/core/integration/README.md | Adds minimal integration library README. |
| libs/core/common/src/lib/models/domain.model.ts | Adds Integration domain + events and payload models for the event bus. |
| libs/core/common/src/lib/models/auth.model.ts | Adds primaryWorkspaceId and AuthenticatedWorkspace type. |
| libs/core/common/src/lib/jobs/jobs.module.ts | Adds global BullMQ root configuration for Redis connection. |
| libs/core/common/src/lib/interfaces/auth.interface.ts | Adds primaryWorkspaceId to UserDTO interface. |
| libs/core/common/src/lib/decorators/index.ts | Exports new CurrentWorkspace decorator. |
| libs/core/common/src/lib/decorators/current-workspace.decorator.ts | Implements CurrentWorkspace decorator (HTTP + GraphQL). |
| libs/core/common/src/lib/db/schema.ts | Adds Integration table/enums and adds primaryWorkspaceId to User schema. |
| libs/core/common/src/lib/common.module.ts | Registers JobsModule in CommonModule. |
| libs/core/auth/src/lib/auth.service.ts | Persists/returns primaryWorkspaceId for users. |
| libs/core/auth/src/lib/auth.queue.ts | Passes workspaceId into user creation as primaryWorkspaceId. |
| docs/guides/slack-setup.md | Adds Slack integration setup documentation. |
| docs/guides/linear-setup.md | Adds Linear integration setup documentation. |
| docs/guides/demo.md | Adds a short demo script for showcasing product value. |
| docs/guides/configuration.md | Adds consolidated configuration/environment variable guide. |
| docs/architecture/tool-uis.md | Documents UI components that render tool outputs. |
| docs/architecture/skills.md | Documents skills structure and how skills are loaded/exposed to agents. |
| docs/architecture/overview.md | Adds high-level system architecture overview. |
| docs/architecture/linea.md | Documents Linea’s architecture/capabilities/data flow. |
| docs/architecture/integrations.md | Documents integrations domain responsibilities and flows. |
| docs/README.md | Adds docs entrypoint/index. |
| apps/customer-ui/src/lib/server-auth.ts | Adds server-side current-user lookup helper for route guards. |
| apps/customer-ui/src/app/onboarding/page.tsx | Defaults integrations onboarding feature flag to enabled. |
| apps/customer-ui/src/app/login/page.tsx | Converts login page into server component that redirects if already authenticated. |
| apps/customer-ui/src/app/login/login-client.tsx | Moves interactive login steps into a client component. |
| apps/customer-ui/src/app/login/loading.tsx | Adds loading UI for login route. |
| apps/customer-ui/src/app/layout.tsx | Normalizes NEXT_PUBLIC_API_URL and tweaks metadata punctuation. |
| apps/customer-ui/src/app/inbox/team/page.tsx | Adds server-side auth guard for team page. |
| apps/customer-ui/src/app/inbox/settings/page.tsx | Replaces large client settings page with server-guarded SettingsClient entrypoint. |
| apps/customer-ui/src/app/inbox/layout.tsx | Removes InboxProvider wrapper (layout deleted). |
| apps/customer-ui/next.config.js | Adds proxy rewrite for /integrations/* routes. |
| apps/core/src/demo-seed.ts | Adds demo seeding script for inbox items, prompt, skills, and heartbeat scheduling. |
| apps/core/src/app/configuration.ts | Adds integrations/app URLs to config schema and env mapping. |
| apps/core/src/app/app.module.ts | Registers IntegrationModule and pipes currentWorkspaceId into GraphQL context. |
| apps/core/project.json | Adds Nx target to run demo seed script. |
| apps/core/migrations/meta/_journal.json | Records new migration entries for integration/user changes. |
| apps/core/migrations/0002_small_squadron_sinister.sql | Updates Integration schema (drop webhook fields, add google enum value). |
| apps/core/migrations/0001_oval_sentinel.sql | Adds Integration table/enums and primaryWorkspaceId to User. |
| README.md | Rewrites root README to a shorter product/demo/dev overview. |
| .env.example | Adds integration/env URL variables and provider secrets placeholders. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (!payload?.prompt || typeof payload.prompt !== 'string') { | ||
| throw new BadRequestException('Prompt is required'); | ||
| } | ||
|
|
||
| const trimmedPrompt = payload.prompt.trim(); | ||
| if (!trimmedPrompt) { | ||
| throw new BadRequestException('Prompt cannot be empty'); | ||
| } |
There was a problem hiding this comment.
BadRequestException is used but not imported in this file. Add it to the @nestjs/common import list so this compiles.
| @Get('/thread/:threadId/state') | ||
| async getThreadState( | ||
| @CurrentUser() currentUser: AuthenticatedUser, | ||
| @CurrentWorkspace() workspace: AuthenticatedWorkspace, | ||
| @Query('threadId') threadId: string, | ||
| ): Promise<StateSnapshot> { |
There was a problem hiding this comment.
threadId is part of the route path (/thread/:threadId/state) but is read from the query string. Replace @Query('threadId') with @Param('threadId') (and import Param) so callers hitting /thread/abc/state work correctly.
| async getThreadState(threadId: string) { | ||
| return this.agent.getState({ | ||
| const agent = await this.agentFactory.getAgentForWorkspace('default'); | ||
| return agent.getState({ | ||
| configurable: { thread_id: threadId }, | ||
| }); | ||
| } |
There was a problem hiding this comment.
This hard-codes the workspace id to 'default', which will return the wrong agent/state for non-default workspaces. Pass the real workspace id into getThreadState (or derive it from context) and use that when calling getAgentForWorkspace.
| const isValid = this.linearService.verifyWebhookSignature( | ||
| rawBody, | ||
| signature, | ||
| this.configService.get('integrations.linear.webhookSecret') as string, |
There was a problem hiding this comment.
The config schema marks integrations.linear.webhookSecret as optional, but this code always passes it into signature verification. If it’s undefined, createHmac will throw and webhook processing will 500. Either (1) make the secret required in config, or (2) explicitly handle the missing-secret case (e.g., skip verification with a clear warning, or reject the webhook with processed: false).
| const isValid = this.linearService.verifyWebhookSignature( | |
| rawBody, | |
| signature, | |
| this.configService.get('integrations.linear.webhookSecret') as string, | |
| const webhookSecret = this.configService.get<string>( | |
| 'integrations.linear.webhookSecret', | |
| ); | |
| if (!webhookSecret) { | |
| this.logger.error( | |
| { organizationId, integrationId: integrationRecord.id }, | |
| 'Linear webhook secret not configured; rejecting webhook', | |
| ); | |
| return { processed: false }; | |
| } | |
| const isValid = this.linearService.verifyWebhookSignature( | |
| rawBody, | |
| signature, | |
| webhookSecret, |
| const expectedSignature = crypto | ||
| .createHmac('sha256', secret) | ||
| .update(payload) | ||
| .digest('hex'); | ||
|
|
||
| // Linear sends the signature as a hex string | ||
| return crypto.timingSafeEqual( | ||
| Buffer.from(signature), | ||
| Buffer.from(expectedSignature), | ||
| ); |
There was a problem hiding this comment.
As written, timingSafeEqual can throw if the incoming signature length differs, which can turn malformed/hostile requests into 500s. Also, Buffer.from(signature) uses UTF-8 bytes, not hex decoding, so you’re comparing ASCII bytes rather than raw digest bytes. Prefer decoding both with Buffer.from(..., 'hex') and guard on equal length (or wrap in try/catch and return false on any error).
| const expectedSignature = crypto | |
| .createHmac('sha256', secret) | |
| .update(payload) | |
| .digest('hex'); | |
| // Linear sends the signature as a hex string | |
| return crypto.timingSafeEqual( | |
| Buffer.from(signature), | |
| Buffer.from(expectedSignature), | |
| ); | |
| try { | |
| const expectedSignatureHex = crypto | |
| .createHmac('sha256', secret) | |
| .update(payload) | |
| .digest('hex'); | |
| // Linear sends the signature as a hex string; decode both values from hex. | |
| const providedSignatureBuffer = Buffer.from(signature, 'hex'); | |
| const expectedSignatureBuffer = Buffer.from(expectedSignatureHex, 'hex'); | |
| // timingSafeEqual throws if buffer lengths differ; guard against that. | |
| if (providedSignatureBuffer.length !== expectedSignatureBuffer.length) { | |
| return false; | |
| } | |
| return crypto.timingSafeEqual( | |
| providedSignatureBuffer, | |
| expectedSignatureBuffer, | |
| ); | |
| } catch (error) { | |
| // On any error (e.g., invalid hex in signature), fail closed. | |
| this.logger.warn( | |
| 'Failed to verify Linear webhook signature', | |
| error as Error, | |
| ); | |
| return false; | |
| } |
| baseURL: config.get<string>('ai.models.primary.modelUrl'), | ||
| }, | ||
| temperature: 0, | ||
| // temperature: 0, |
There was a problem hiding this comment.
Commenting out temperature changes runtime model behavior (and can reduce determinism). If the intent is deterministic outputs for agents/classifiers, set an explicit value instead of leaving a commented line (and remove the extra indentation in the comment).
| const candidates = results | ||
| .map((item) => item.value as unknown as MemoryItem) | ||
| .filter((memory) => memory.category === 'instruction'); | ||
|
|
||
| if (candidates.length === 0) { | ||
| return null; | ||
| } |
There was a problem hiding this comment.
This selects the latest memory with category === 'instruction' but doesn’t scope to the specific record you write (summary = 'Workspace instructions for Linea'). If multiple instruction memories exist in the workspace, this can return the wrong prompt. Filter by both category and the expected summary (or store under a stable key/namespace) to make retrieval unambiguous.
|
|
||
| if (linearMatch || slackMatch) { | ||
| linkedIdentities.push({ | ||
| id: `identity-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`, |
There was a problem hiding this comment.
Using Date.now() + Math.random() for IDs makes collisions unlikely but still possible, and it’s harder to test/debug. Prefer randomUUID() (Node crypto) for stable uniqueness and consistent formatting.
| async updateInboxStatus(threadId: string, status: string): Promise<void> { | ||
| const thread = await this.getThread(threadId); | ||
|
|
||
| if (!thread || !thread.isInboxThread) { | ||
| this.logger.warn( | ||
| `Cannot update inbox status for non-inbox thread: ${threadId}`, | ||
| ); | ||
| return; | ||
| } |
There was a problem hiding this comment.
status is typed as string, but the file already imports InboxStatus. Use the union/enum type here so callers can’t write invalid statuses (and so the archive-on-resolution logic is type-checked).
| verifyWebhookSignature( | ||
| payload: string, | ||
| signature: string, | ||
| secret: string, | ||
| ): boolean { |
There was a problem hiding this comment.
The integration library has Jest configured, but this signature verification logic is security-critical and should be unit-tested (valid signature, invalid signature, wrong-length signature, missing/empty signature) to prevent regressions and accidental 500s.
- Adjust type definition for event message handling - Improve import paths in inbox-messages component - Optimize GraphQL query and subscription data types - Update dialog component syntax for consistency
- Introduce TypeScript interfaces for GitHub API responses - Add TypeScript type assertions to improve type safety - Modify GraphQL mutations for consistency - Update UI code for readability and maintainability
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 90 out of 160 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @Body() payload: { prompt?: string }, | ||
| ): Promise<{ prompt: string }> { | ||
| if (!payload?.prompt || typeof payload.prompt !== 'string') { | ||
| throw new BadRequestException('Prompt is required'); | ||
| } |
There was a problem hiding this comment.
BadRequestException is used but not imported in this file, which will fail compilation. Add BadRequestException to the @nestjs/common imports at the top of the file.
| const aTime = new Date(a.createdAt || a.updatedAt || 0).getTime(); | ||
| const bTime = new Date(b.createdAt || b.updatedAt || 0).getTime(); |
There was a problem hiding this comment.
This sorting prioritizes createdAt over updatedAt (createdAt will almost always exist), so recently-updated threads won’t float to the top. Swap the precedence to updatedAt first (updatedAt || createdAt) to reflect recency of activity.
| const aTime = new Date(a.createdAt || a.updatedAt || 0).getTime(); | |
| const bTime = new Date(b.createdAt || b.updatedAt || 0).getTime(); | |
| const aTime = new Date(a.updatedAt || a.createdAt || 0).getTime(); | |
| const bTime = new Date(b.updatedAt || b.createdAt || 0).getTime(); |
| return { remoteId: threadId }; | ||
| } | ||
|
|
||
| async updateInboxStatus(threadId: string, status: string): Promise<void> { |
There was a problem hiding this comment.
updateInboxStatus accepts an untyped string, but the rest of the code treats inboxStatus as a constrained enum/union (InboxStatus). Accepting arbitrary strings makes it easy to persist invalid states and break filtering/UI. Change the parameter type to InboxStatus (and validate inputs if coming from user/tooling).
| async updateInboxStatus(threadId: string, status: string): Promise<void> { | |
| async updateInboxStatus( | |
| threadId: string, | |
| status: InboxStatus, | |
| ): Promise<void> { |
| const isValid = this.linearService.verifyWebhookSignature( | ||
| rawBody, | ||
| signature, | ||
| this.configService.get('integrations.linear.webhookSecret') as string, | ||
| ); | ||
|
|
||
| if (!isValid) { | ||
| this.logger.error( | ||
| { organizationId, integrationId: integrationRecord.id }, | ||
| 'Invalid Linear webhook signature', | ||
| ); | ||
|
|
||
| return { processed: false }; |
There was a problem hiding this comment.
integrations.linear.webhookSecret is configured as optional, but this code always verifies the signature and casts the secret to string. If the secret is unset, signature verification will fail (or behave incorrectly) and the webhook will always be rejected. Only verify the signature when a secret is configured; otherwise accept the webhook (or explicitly require a secret and fail fast at startup).
| const isValid = this.linearService.verifyWebhookSignature( | |
| rawBody, | |
| signature, | |
| this.configService.get('integrations.linear.webhookSecret') as string, | |
| ); | |
| if (!isValid) { | |
| this.logger.error( | |
| { organizationId, integrationId: integrationRecord.id }, | |
| 'Invalid Linear webhook signature', | |
| ); | |
| return { processed: false }; | |
| const webhookSecret = this.configService.get<string>( | |
| 'integrations.linear.webhookSecret', | |
| ); | |
| if (webhookSecret) { | |
| const isValid = this.linearService.verifyWebhookSignature( | |
| rawBody, | |
| signature, | |
| webhookSecret, | |
| ); | |
| if (!isValid) { | |
| this.logger.error( | |
| { organizationId, integrationId: integrationRecord.id }, | |
| 'Invalid Linear webhook signature', | |
| ); | |
| return { processed: false }; | |
| } | |
| } else { | |
| this.logger.warn( | |
| { organizationId, integrationId: integrationRecord.id }, | |
| 'Linear webhook secret not configured; skipping signature verification', | |
| ); |
| private async saveSettingsRecord( | ||
| workspaceId: string, | ||
| updatedBy: string, | ||
| settings: HeartbeatSettings, | ||
| existing: HeartbeatSettings | null, | ||
| ): Promise<void> { |
There was a problem hiding this comment.
The saveSettingsRecord method takes an existing parameter but does not use it anywhere. Remove the unused parameter (and corresponding call-site argument) or refactor save/update so the “existing memory record” is passed through to avoid duplicated search/sort work and reduce drift between getSettingsRecord and getSettingsMemory.
| await this.store.put( | ||
| ['filesystem'], | ||
| path, | ||
| fileData as unknown as Record<string, string>, |
There was a problem hiding this comment.
fileData is being forced into Record<string, string>, but the public getSkillFiles() return type includes content: string[] (not string). This cast can hide real shape mismatches and lead to runtime incompatibilities for consumers reading from the store. Store the value with an accurate type (e.g., Record<string, unknown>) or normalize/serialize the structure consistently (e.g., content joined to a string) before persisting.
| fileData as unknown as Record<string, string>, | |
| fileData as unknown as Record<string, unknown>, |
- Refactor import statements for efficiency - Harmonize LangChain message type handling - Fix type definition in LaunchlineRuntimeProvider
- Add `BadRequestException` for improved error management - Ensure consistent use of TypeScript optional chaining - Format code for better readability and maintainability
- Introduce unit test to ensure IntegrationModule is defined
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 91 out of 161 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @Body() payload: { prompt?: string }, | ||
| ): Promise<{ prompt: string }> { | ||
| if (!payload?.prompt || typeof payload.prompt !== 'string') { | ||
| throw new BadRequestException('Prompt is required'); | ||
| } | ||
|
|
||
| const trimmedPrompt = payload.prompt.trim(); | ||
| if (!trimmedPrompt) { | ||
| throw new BadRequestException('Prompt cannot be empty'); | ||
| } |
There was a problem hiding this comment.
BadRequestException is used but not imported from '@nestjs/common' in this file, which will fail compilation. Import BadRequestException alongside the other Nest imports.
| @Get('/thread/:threadId/state') | ||
| async getThreadState( | ||
| @CurrentUser() currentUser: AuthenticatedUser, | ||
| @CurrentWorkspace() workspace: AuthenticatedWorkspace, | ||
| @Query('threadId') threadId: string, | ||
| ): Promise<StateSnapshot> { | ||
| const workspace = await this.workspaceFacade.getWorkspaceByUserId( | ||
| currentUser.userId, | ||
| ); | ||
|
|
||
| return this.agent.graph.getState({ | ||
| const agent = await this.agentFactory.getAgentForWorkspace(workspace.id); | ||
| return agent.graph.getState({ | ||
| configurable: { | ||
| thread_id: threadId, | ||
| workspaceId: workspace.id, |
There was a problem hiding this comment.
| async getThreadState(threadId: string) { | ||
| return this.agent.getState({ | ||
| const agent = await this.agentFactory.getAgentForWorkspace('default'); | ||
| return agent.getState({ | ||
| configurable: { thread_id: threadId }, | ||
| }); | ||
| } |
There was a problem hiding this comment.
getThreadState() now hardcodes workspaceId = 'default', which will ignore workspace-scoped prompts/skills and can return the wrong state if checkpoints are partitioned by workspaceId in the config. Consider requiring workspaceId as a parameter, or resolving it from the stored thread and calling getAgentForWorkspace(thread.workspaceId).
| const candidates = results | ||
| .map((item) => item.value as unknown as MemoryItem) | ||
| .filter((memory) => memory.category === 'instruction'); |
There was a problem hiding this comment.
getPromptMemory filters only by category === 'instruction' and does not scope by a distinct summary/key. If other instruction memories exist in the workspace, this can select the wrong record (latest instruction wins). Consider also filtering by a specific summary (e.g., 'Workspace instructions for Linea') or a dedicated category to avoid collisions.
| apiKey: config.get<string>('ai.models.primary.apiKey'), | ||
| baseURL: config.get<string>('ai.models.primary.modelUrl'), | ||
| }, | ||
| temperature: 0, | ||
| // temperature: 0, | ||
| }), |
There was a problem hiding this comment.
Commenting out temperature changes model determinism (defaults vary by provider/version) and leaves a stray mis-indented comment in production code. Prefer setting an explicit temperature (even if 0) or removing the commented line entirely.
Summary
Short description of the change.
Related issue(s)
Fixes:
Type of change
Component
Description
A more detailed description of changes and rationale.
How to test
n/a
Checklist
Screenshots (if UI)
n/a
Release notes
First version of Linea - product copilot