v0.5.10: copilot upgrade, preprocessor, logs search, UI, code hygiene

This commit is contained in:
Waleed
2025-11-21 12:04:34 -08:00
committed by GitHub
596 changed files with 18250 additions and 10285 deletions

View File

@@ -374,7 +374,7 @@ In addition, you will need to update the registries:
Add your block to the blocks registry (`/apps/sim/blocks/registry.ts`):
```typescript:/apps/sim/blocks/registry.ts
import { PineconeBlock } from './blocks/pinecone'
import { PineconeBlock } from '@/blocks/blocks/pinecone'
// Registry of all available blocks
export const registry: Record<string, BlockConfig> = {

View File

@@ -21,7 +21,7 @@ jobs:
name: Build AMD64
needs: test-build
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging')
runs-on: blacksmith-4vcpu-ubuntu-2404
runs-on: blacksmith-8vcpu-ubuntu-2404
permissions:
contents: read
packages: write
@@ -162,7 +162,7 @@ jobs:
# Create GHCR multi-arch manifests (only for main, after both builds)
create-ghcr-manifests:
name: Create GHCR Manifests
runs-on: blacksmith-4vcpu-ubuntu-2404
runs-on: blacksmith-8vcpu-ubuntu-2404
needs: [build-amd64, build-ghcr-arm64]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
permissions:
@@ -198,18 +198,30 @@ jobs:
"${IMAGE_BASE}:${{ github.sha }}-arm64"
docker manifest push "${IMAGE_BASE}:${{ github.sha }}"
# Deploy Trigger.dev (after ECR images are pushed, runs in parallel with process-docs)
trigger-deploy:
name: Deploy Trigger.dev
needs: build-amd64
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging')
uses: ./.github/workflows/trigger-deploy.yml
secrets: inherit
# Check if docs changed
check-docs-changes:
name: Check Docs Changes
runs-on: blacksmith-4vcpu-ubuntu-2404
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
outputs:
docs_changed: ${{ steps.filter.outputs.docs }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2 # Need at least 2 commits to detect changes
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
docs:
- 'apps/docs/content/docs/en/**'
- 'apps/sim/scripts/process-docs.ts'
- 'apps/sim/lib/chunkers/**'
# Process docs embeddings (after ECR images are pushed, runs in parallel with trigger-deploy)
# Process docs embeddings (only when docs change, after ECR images are pushed)
process-docs:
name: Process Docs
needs: build-amd64
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging')
needs: [build-amd64, check-docs-changes]
if: needs.check-docs-changes.outputs.docs_changed == 'true'
uses: ./.github/workflows/docs-embeddings.yml
secrets: inherit

View File

@@ -7,8 +7,8 @@ on:
jobs:
process-docs-embeddings:
name: Process Documentation Embeddings
runs-on: blacksmith-4vcpu-ubuntu-2404
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging'
runs-on: blacksmith-8vcpu-ubuntu-2404
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout code
@@ -41,6 +41,6 @@ jobs:
- name: Process docs embeddings
working-directory: ./apps/sim
env:
DATABASE_URL: ${{ github.ref == 'refs/heads/main' && secrets.DATABASE_URL || secrets.STAGING_DATABASE_URL }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: bun run scripts/process-docs.ts --clear

View File

@@ -12,7 +12,7 @@ permissions:
jobs:
build-amd64:
name: Build AMD64
runs-on: blacksmith-4vcpu-ubuntu-2404
runs-on: blacksmith-8vcpu-ubuntu-2404
strategy:
fail-fast: false
matrix:
@@ -146,7 +146,7 @@ jobs:
create-ghcr-manifests:
name: Create GHCR Manifests
runs-on: blacksmith-4vcpu-ubuntu-2404
runs-on: blacksmith-8vcpu-ubuntu-2404
needs: [build-amd64, build-ghcr-arm64]
if: github.ref == 'refs/heads/main'
strategy:

View File

@@ -1,55 +0,0 @@
name: Trigger.dev Deploy
on:
workflow_call:
workflow_dispatch:
jobs:
deploy:
name: Deploy to Trigger.dev
runs-on: blacksmith-4vcpu-ubuntu-2404
concurrency:
group: trigger-deploy-${{ github.ref }}
cancel-in-progress: false
env:
TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }}
TRIGGER_PROJECT_ID: ${{ secrets.TRIGGER_PROJECT_ID }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: latest
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.22
- name: Cache Bun dependencies
uses: actions/cache@v4
with:
path: |
~/.bun/install/cache
node_modules
**/node_modules
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Deploy to Trigger.dev (Staging)
if: github.ref == 'refs/heads/staging'
working-directory: ./apps/sim
run: npx --yes trigger.dev@4.0.4 deploy -e staging
- name: Deploy to Trigger.dev (Production)
if: github.ref == 'refs/heads/main'
working-directory: ./apps/sim
run: npx --yes trigger.dev@4.0.4 deploy

View File

@@ -1,7 +1,7 @@
'use client'
import type * as React from 'react'
import { blockTypeToIconMap } from './icon-mapping'
import { blockTypeToIconMap } from '@/components/ui/icon-mapping'
interface BlockInfoCardProps {
type: string

View File

@@ -2,8 +2,8 @@
import { useState } from 'react'
import NextImage, { type ImageProps as NextImageProps } from 'next/image'
import { Lightbox } from '@/components/ui/lightbox'
import { cn } from '@/lib/utils'
import { Lightbox } from './lightbox'
interface ImageProps extends Omit<NextImageProps, 'className'> {
className?: string

View File

@@ -39,14 +39,24 @@ Tickets von Linear abrufen und filtern
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `teamId` | string | Ja | Linear Team-ID |
| `projectId` | string | Ja | Linear Projekt-ID |
| `teamId` | string | Nein | Linear Team-ID zum Filtern |
| `projectId` | string | Nein | Linear Projekt-ID zum Filtern |
| `assigneeId` | string | Nein | Benutzer-ID zum Filtern nach Zugewiesenem |
| `stateId` | string | Nein | Workflow-Status-ID zum Filtern nach Status |
| `priority` | number | Nein | Priorität zum Filtern \(0=Keine Priorität, 1=Dringend, 2=Hoch, 3=Normal, 4=Niedrig\) |
| `labelIds` | array | Nein | Array von Label-IDs zum Filtern |
| `createdAfter` | string | Nein | Tickets filtern, die nach diesem Datum erstellt wurden \(ISO 8601 Format\) |
| `updatedAfter` | string | Nein | Tickets filtern, die nach diesem Datum aktualisiert wurden \(ISO 8601 Format\) |
| `includeArchived` | boolean | Nein | Archivierte Tickets einschließen \(Standard: false\) |
| `first` | number | Nein | Anzahl der zurückzugebenden Tickets \(Standard: 50, max: 250\) |
| `after` | string | Nein | Paginierungscursor für die nächste Seite |
| `orderBy` | string | Nein | Sortierreihenfolge: "createdAt" oder "updatedAt" \(Standard: "updatedAt"\) |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `issues` | array | Array von Tickets aus dem angegebenen Linear Team und Projekt, jedes enthält id, title, description, state, teamId und projectId |
| `issues` | array | Array von gefilterten Tickets aus Linear |
### `linear_get_issue`
@@ -73,15 +83,25 @@ Ein neues Ticket in Linear erstellen
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `teamId` | string | Ja | Linear Team-ID |
| `projectId` | string | Ja | Linear Projekt-ID |
| `projectId` | string | Nein | Linear Projekt-ID |
| `title` | string | Ja | Ticket-Titel |
| `description` | string | Nein | Ticket-Beschreibung |
| `stateId` | string | Nein | Workflow-Status-ID \(Status\) |
| `assigneeId` | string | Nein | Benutzer-ID, dem das Ticket zugewiesen werden soll |
| `priority` | number | Nein | Priorität \(0=Keine Priorität, 1=Dringend, 2=Hoch, 3=Normal, 4=Niedrig\) |
| `estimate` | number | Nein | Schätzung in Punkten |
| `labelIds` | array | Nein | Array von Label-IDs, die dem Ticket zugewiesen werden sollen |
| `cycleId` | string | Nein | Zyklus-ID, dem das Ticket zugewiesen werden soll |
| `parentId` | string | Nein | Übergeordnete Ticket-ID \(für die Erstellung von Untertickets\) |
| `dueDate` | string | Nein | Fälligkeitsdatum im ISO 8601-Format \(nur Datum: JJJJ-MM-TT\) |
| `subscriberIds` | array | Nein | Array von Benutzer-IDs, die das Ticket abonnieren sollen |
| `projectMilestoneId` | string | Nein | Projektmeilenstein-ID, die mit dem Ticket verknüpft werden soll |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `issue` | object | Das erstellte Ticket mit id, title, description, state, teamId und projectId |
| `issue` | object | Das erstellte Ticket mit allen seinen Eigenschaften |
### `linear_update_issue`
@@ -98,7 +118,13 @@ Ein bestehendes Ticket in Linear aktualisieren
| `assigneeId` | string | Nein | Benutzer-ID, dem das Ticket zugewiesen werden soll |
| `priority` | number | Nein | Priorität \(0=Keine Priorität, 1=Dringend, 2=Hoch, 3=Normal, 4=Niedrig\) |
| `estimate` | number | Nein | Schätzung in Punkten |
| `labelIds` | array | Nein | Array von Label-IDs, die dem Ticket zugewiesen werden sollen |
| `labelIds` | array | Nein | Array von Label-IDs, die für das Ticket gesetzt werden sollen \(ersetzt alle vorhandenen Labels\) |
| `projectId` | string | Nein | Projekt-ID, zu der das Ticket verschoben werden soll |
| `cycleId` | string | Nein | Zyklus-ID, dem das Ticket zugewiesen werden soll |
| `parentId` | string | Nein | Übergeordnete Ticket-ID \(um dieses zu einem Unterticket zu machen\) |
| `dueDate` | string | Nein | Fälligkeitsdatum im ISO 8601-Format \(nur Datum: JJJJ-MM-TT\) |
| `addedLabelIds` | array | Nein | Array von Label-IDs, die dem Ticket hinzugefügt werden sollen \(ohne vorhandene Labels zu ersetzen\) |
| `removedLabelIds` | array | Nein | Array von Label-IDs, die vom Ticket entfernt werden sollen |
#### Ausgabe
@@ -236,8 +262,8 @@ Einen Kommentar in Linear bearbeiten
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `commentId` | string | Ja | Kommentar-ID, die aktualisiert werden soll |
| `body` | string | Ja | Neuer Kommentartext \(unterstützt Markdown\) |
| `commentId` | string | Ja | Kommentar-ID zum Aktualisieren |
| `body` | string | Nein | Neuer Kommentartext \(unterstützt Markdown\) |
#### Ausgabe
@@ -344,14 +370,14 @@ Ein bestehendes Projekt in Linear aktualisieren
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Ja | Projekt-ID, die aktualisiert werden soll |
| `projectId` | string | Ja | Projekt-ID zum Aktualisieren |
| `name` | string | Nein | Neuer Projektname |
| `description` | string | Nein | Neue Projektbeschreibung |
| `state` | string | Nein | Projektstatus (geplant, gestartet, abgeschlossen, abgebrochen) |
| `state` | string | Nein | Projektstatus \(planned, started, completed, canceled\) |
| `leadId` | string | Nein | Benutzer-ID des Projektleiters |
| `startDate` | string | Nein | Projektstartdatum (ISO-Format) |
| `targetDate` | string | Nein | Projektzieldatum (ISO-Format) |
| `priority` | number | Nein | Projektpriorität (0-4) |
| `startDate` | string | Nein | Projektstartdatum \(ISO-Format: JJJJ-MM-TT\) |
| `targetDate` | string | Nein | Projektzieldatum \(ISO-Format: JJJJ-MM-TT\) |
| `priority` | number | Nein | Projektpriorität \(0=Keine Priorität, 1=Dringend, 2=Hoch, 3=Normal, 4=Niedrig\) |
#### Ausgabe
@@ -525,11 +551,11 @@ Einen neuen Workflow-Status in Linear erstellen
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `teamId` | string | Ja | Team-ID, in dem der Status erstellt werden soll |
| `name` | string | Ja | Name des Status \(z.B. "In Prüfung"\) |
| `color` | string | Ja | Farbe des Status \(Hex-Format\) |
| `teamId` | string | Ja | Team-ID, in der der Status erstellt werden soll |
| `name` | string | Ja | Statusname \(z.B. "In Prüfung"\) |
| `color` | string | Nein | Statusfarbe \(Hex-Format\) |
| `type` | string | Ja | Statustyp: "backlog", "unstarted", "started", "completed" oder "canceled" |
| `description` | string | Nein | Beschreibung des Status |
| `description` | string | Nein | Statusbeschreibung |
| `position` | number | Nein | Position im Workflow |
#### Ausgabe
@@ -635,9 +661,9 @@ Einen Anhang zu einem Ticket in Linear hinzufügen
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `issueId` | string | Ja | Ticket-ID, an die der Anhang angehängt werden soll |
| `issueId` | string | Ja | Ticket-ID, an die angehängt werden soll |
| `url` | string | Ja | URL des Anhangs |
| `title` | string | Nein | Titel des Anhangs |
| `title` | string | Ja | Titel des Anhangs |
| `subtitle` | string | Nein | Untertitel/Beschreibung des Anhangs |
#### Ausgabe
@@ -673,7 +699,7 @@ Metadaten eines Anhangs in Linear aktualisieren
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `attachmentId` | string | Ja | Anhang-ID zum Aktualisieren |
| `title` | string | Nein | Neuer Titel des Anhangs |
| `title` | string | Ja | Neuer Titel des Anhangs |
| `subtitle` | string | Nein | Neuer Untertitel des Anhangs |
#### Ausgabe
@@ -707,8 +733,8 @@ Zwei Tickets in Linear miteinander verknüpfen (blockiert, bezieht sich auf, dup
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `issueId` | string | Ja | Quell-Ticket-ID |
| `relatedIssueId` | string | Ja | Ziel-Ticket-ID für die Verknüpfung |
| `type` | string | Ja | Beziehungstyp: "blocks", "blocked", "duplicate", "related" |
| `relatedIssueId` | string | Ja | Ziel-Ticket-ID, mit der verknüpft werden soll |
| `type` | string | Ja | Beziehungstyp: "blocks", "duplicate" oder "related". Hinweis: Wenn "blocks" von A nach B erstellt wird, wird die umgekehrte Beziehung \(B blockiert durch A\) automatisch erstellt. |
#### Ausgabe
@@ -1217,7 +1243,8 @@ Ein neues Projekt-Label in Linear erstellen
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `name` | string | Ja | Projekt-Label-Name |
| `projectId` | string | Ja | Das Projekt für dieses Label |
| `name` | string | Ja | Projektlabel-Name |
| `color` | string | Nein | Label-Farbe \(Hex-Code\) |
| `description` | string | Nein | Label-Beschreibung |
| `isGroup` | boolean | Nein | Ob dies eine Label-Gruppe ist |
@@ -1394,6 +1421,7 @@ Einen neuen Projektstatus in Linear erstellen
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Ja | Das Projekt, für das der Status erstellt werden soll |
| `name` | string | Ja | Name des Projektstatus |
| `color` | string | Ja | Statusfarbe \(Hex-Code\) |
| `description` | string | Nein | Statusbeschreibung |

View File

@@ -44,12 +44,22 @@ Fetch and filter issues from Linear
| --------- | ---- | -------- | ----------- |
| `teamId` | string | No | Linear team ID to filter by |
| `projectId` | string | No | Linear project ID to filter by |
| `assigneeId` | string | No | User ID to filter by assignee |
| `stateId` | string | No | Workflow state ID to filter by status |
| `priority` | number | No | Priority to filter by \(0=No priority, 1=Urgent, 2=High, 3=Normal, 4=Low\) |
| `labelIds` | array | No | Array of label IDs to filter by |
| `createdAfter` | string | No | Filter issues created after this date \(ISO 8601 format\) |
| `updatedAfter` | string | No | Filter issues updated after this date \(ISO 8601 format\) |
| `includeArchived` | boolean | No | Include archived issues \(default: false\) |
| `first` | number | No | Number of issues to return \(default: 50, max: 250\) |
| `after` | string | No | Pagination cursor for next page |
| `orderBy` | string | No | Sort order: "createdAt" or "updatedAt" \(default: "updatedAt"\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `issues` | array | Array of issues from the specified Linear team and project, each containing id, title, description, state, teamId, and projectId |
| `issues` | array | Array of filtered issues from Linear |
### `linear_get_issue`
@@ -79,12 +89,22 @@ Create a new issue in Linear
| `projectId` | string | No | Linear project ID |
| `title` | string | Yes | Issue title |
| `description` | string | No | Issue description |
| `stateId` | string | No | Workflow state ID \(status\) |
| `assigneeId` | string | No | User ID to assign the issue to |
| `priority` | number | No | Priority \(0=No priority, 1=Urgent, 2=High, 3=Normal, 4=Low\) |
| `estimate` | number | No | Estimate in points |
| `labelIds` | array | No | Array of label IDs to set on the issue |
| `cycleId` | string | No | Cycle ID to assign the issue to |
| `parentId` | string | No | Parent issue ID \(for creating sub-issues\) |
| `dueDate` | string | No | Due date in ISO 8601 format \(date only: YYYY-MM-DD\) |
| `subscriberIds` | array | No | Array of user IDs to subscribe to the issue |
| `projectMilestoneId` | string | No | Project milestone ID to associate with the issue |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `issue` | object | The created issue containing id, title, description, state, teamId, and projectId |
| `issue` | object | The created issue with all its properties |
### `linear_update_issue`
@@ -101,7 +121,13 @@ Update an existing issue in Linear
| `assigneeId` | string | No | User ID to assign the issue to |
| `priority` | number | No | Priority \(0=No priority, 1=Urgent, 2=High, 3=Normal, 4=Low\) |
| `estimate` | number | No | Estimate in points |
| `labelIds` | array | No | Array of label IDs to set on the issue |
| `labelIds` | array | No | Array of label IDs to set on the issue \(replaces all existing labels\) |
| `projectId` | string | No | Project ID to move the issue to |
| `cycleId` | string | No | Cycle ID to assign the issue to |
| `parentId` | string | No | Parent issue ID \(for making this a sub-issue\) |
| `dueDate` | string | No | Due date in ISO 8601 format \(date only: YYYY-MM-DD\) |
| `addedLabelIds` | array | No | Array of label IDs to add to the issue \(without replacing existing labels\) |
| `removedLabelIds` | array | No | Array of label IDs to remove from the issue |
#### Output
@@ -352,9 +378,9 @@ Update an existing project in Linear
| `description` | string | No | New project description |
| `state` | string | No | Project state \(planned, started, completed, canceled\) |
| `leadId` | string | No | User ID of the project lead |
| `startDate` | string | No | Project start date \(ISO format\) |
| `targetDate` | string | No | Project target date \(ISO format\) |
| `priority` | number | No | Project priority \(0-4\) |
| `startDate` | string | No | Project start date \(ISO format: YYYY-MM-DD\) |
| `targetDate` | string | No | Project target date \(ISO format: YYYY-MM-DD\) |
| `priority` | number | No | Project priority \(0=No priority, 1=Urgent, 2=High, 3=Normal, 4=Low\) |
#### Output
@@ -530,7 +556,7 @@ Create a new workflow state (status) in Linear
| --------- | ---- | -------- | ----------- |
| `teamId` | string | Yes | Team ID to create the state in |
| `name` | string | Yes | State name \(e.g., "In Review"\) |
| `color` | string | Yes | State color \(hex format\) |
| `color` | string | No | State color \(hex format\) |
| `type` | string | Yes | State type: "backlog", "unstarted", "started", "completed", or "canceled" |
| `description` | string | No | State description |
| `position` | number | No | Position in the workflow |
@@ -711,7 +737,7 @@ Link two issues together in Linear (blocks, relates to, duplicates)
| --------- | ---- | -------- | ----------- |
| `issueId` | string | Yes | Source issue ID |
| `relatedIssueId` | string | Yes | Target issue ID to link to |
| `type` | string | Yes | Relation type: "blocks", "blocked", "duplicate", "related" |
| `type` | string | Yes | Relation type: "blocks", "duplicate", or "related". Note: When creating "blocks" from A to B, the inverse relation \(B blocked by A\) is automatically created. |
#### Output
@@ -1220,6 +1246,7 @@ Create a new project label in Linear
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | The project for this label |
| `name` | string | Yes | Project label name |
| `color` | string | No | Label color \(hex code\) |
| `description` | string | No | Label description |
@@ -1397,6 +1424,7 @@ Create a new project status in Linear
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | The project to create the status for |
| `name` | string | Yes | Project status name |
| `color` | string | Yes | Status color \(hex code\) |
| `description` | string | No | Status description |

View File

@@ -39,14 +39,24 @@ Obtener y filtrar incidencias de Linear
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | ----------- | ----------- |
| `teamId` | string | | ID del equipo de Linear |
| `projectId` | string | | ID del proyecto de Linear |
| `teamId` | string | No | ID del equipo de Linear para filtrar |
| `projectId` | string | No | ID del proyecto de Linear para filtrar |
| `assigneeId` | string | No | ID del usuario para filtrar por asignado |
| `stateId` | string | No | ID del estado del flujo de trabajo para filtrar por estado |
| `priority` | number | No | Prioridad para filtrar \(0=Sin prioridad, 1=Urgente, 2=Alta, 3=Normal, 4=Baja\) |
| `labelIds` | array | No | Array de IDs de etiquetas para filtrar |
| `createdAfter` | string | No | Filtrar incidencias creadas después de esta fecha \(formato ISO 8601\) |
| `updatedAfter` | string | No | Filtrar incidencias actualizadas después de esta fecha \(formato ISO 8601\) |
| `includeArchived` | boolean | No | Incluir incidencias archivadas \(predeterminado: false\) |
| `first` | number | No | Número de incidencias a devolver \(predeterminado: 50, máximo: 250\) |
| `after` | string | No | Cursor de paginación para la siguiente página |
| `orderBy` | string | No | Orden de clasificación: "createdAt" o "updatedAt" \(predeterminado: "updatedAt"\) |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `issues` | array | Array de incidencias del equipo y proyecto de Linear especificados, cada una contiene id, título, descripción, estado, teamId y projectId |
| `issues` | array | Array de incidencias filtradas de Linear |
### `linear_get_issue`
@@ -73,15 +83,25 @@ Crear una nueva incidencia en Linear
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | ----------- | ----------- |
| `teamId` | string | Sí | ID del equipo de Linear |
| `projectId` | string | | ID del proyecto de Linear |
| `projectId` | string | No | ID del proyecto de Linear |
| `title` | string | Sí | Título de la incidencia |
| `description` | string | No | Descripción de la incidencia |
| `stateId` | string | No | ID del estado del flujo de trabajo \(estado\) |
| `assigneeId` | string | No | ID del usuario al que asignar la incidencia |
| `priority` | number | No | Prioridad \(0=Sin prioridad, 1=Urgente, 2=Alta, 3=Normal, 4=Baja\) |
| `estimate` | number | No | Estimación en puntos |
| `labelIds` | array | No | Array de IDs de etiquetas para establecer en la incidencia |
| `cycleId` | string | No | ID del ciclo al que asignar la incidencia |
| `parentId` | string | No | ID de la incidencia padre \(para crear subincidencias\) |
| `dueDate` | string | No | Fecha de vencimiento en formato ISO 8601 \(solo fecha: AAAA-MM-DD\) |
| `subscriberIds` | array | No | Array de IDs de usuarios para suscribirse a la incidencia |
| `projectMilestoneId` | string | No | ID del hito del proyecto para asociar con la incidencia |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `issue` | object | La incidencia creada que contiene id, título, descripción, estado, teamId y projectId |
| `issue` | object | La incidencia creada con todas sus propiedades |
### `linear_update_issue`
@@ -91,14 +111,20 @@ Actualizar una incidencia existente en Linear
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | ----------- | ----------- |
| `issueId` | string | Sí | ID de la incidencia de Linear a actualizar |
| `issueId` | string | Sí | ID de la incidencia de Linear para actualizar |
| `title` | string | No | Nuevo título de la incidencia |
| `description` | string | No | Nueva descripción de la incidencia |
| `stateId` | string | No | ID del estado del flujo de trabajo \(estado\) |
| `assigneeId` | string | No | ID del usuario al que asignar la incidencia |
| `priority` | number | No | Prioridad \(0=Sin prioridad, 1=Urgente, 2=Alta, 3=Normal, 4=Baja\) |
| `estimate` | number | No | Estimación en puntos |
| `labelIds` | array | No | Array de IDs de etiquetas para establecer en la incidencia |
| `labelIds` | array | No | Array de IDs de etiquetas para establecer en la incidencia \(reemplaza todas las etiquetas existentes\) |
| `projectId` | string | No | ID del proyecto al que mover la incidencia |
| `cycleId` | string | No | ID del ciclo al que asignar la incidencia |
| `parentId` | string | No | ID de la incidencia padre \(para convertir esta en una subincidencia\) |
| `dueDate` | string | No | Fecha de vencimiento en formato ISO 8601 \(solo fecha: AAAA-MM-DD\) |
| `addedLabelIds` | array | No | Array de IDs de etiquetas para añadir a la incidencia \(sin reemplazar las etiquetas existentes\) |
| `removedLabelIds` | array | No | Array de IDs de etiquetas para eliminar de la incidencia |
#### Salida
@@ -237,7 +263,7 @@ Editar un comentario en Linear
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | ----------- | ----------- |
| `commentId` | string | Sí | ID del comentario a actualizar |
| `body` | string | | Nuevo texto del comentario \(admite Markdown\) |
| `body` | string | No | Nuevo texto del comentario \(admite Markdown\) |
#### Salida
@@ -347,11 +373,11 @@ Actualizar un proyecto existente en Linear
| `projectId` | string | Sí | ID del proyecto a actualizar |
| `name` | string | No | Nuevo nombre del proyecto |
| `description` | string | No | Nueva descripción del proyecto |
| `state` | string | No | Estado del proyecto (planned, started, completed, canceled) |
| `state` | string | No | Estado del proyecto \(planned, started, completed, canceled\) |
| `leadId` | string | No | ID del usuario líder del proyecto |
| `startDate` | string | No | Fecha de inicio del proyecto (formato ISO) |
| `targetDate` | string | No | Fecha objetivo del proyecto (formato ISO) |
| `priority` | number | No | Prioridad del proyecto (0-4) |
| `startDate` | string | No | Fecha de inicio del proyecto \(formato ISO: YYYY-MM-DD\) |
| `targetDate` | string | No | Fecha objetivo del proyecto \(formato ISO: YYYY-MM-DD\) |
| `priority` | number | No | Prioridad del proyecto \(0=Sin prioridad, 1=Urgente, 2=Alta, 3=Normal, 4=Baja\) |
#### Salida
@@ -526,8 +552,8 @@ Crear un nuevo estado de flujo de trabajo (estado) en Linear
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | ----------- | ----------- |
| `teamId` | string | Sí | ID del equipo donde crear el estado |
| `name` | string | Sí | Nombre del estado (p. ej., "En revisión") |
| `color` | string | | Color del estado (formato hex) |
| `name` | string | Sí | Nombre del estado \(p. ej., "En revisión"\) |
| `color` | string | No | Color del estado \(formato hex\) |
| `type` | string | Sí | Tipo de estado: "backlog", "unstarted", "started", "completed", o "canceled" |
| `description` | string | No | Descripción del estado |
| `position` | number | No | Posición en el flujo de trabajo |
@@ -637,7 +663,7 @@ Añadir un adjunto a una incidencia en Linear
| --------- | ---- | ----------- | ----------- |
| `issueId` | string | Sí | ID de la incidencia a la que adjuntar |
| `url` | string | Sí | URL del adjunto |
| `title` | string | No | Título del adjunto |
| `title` | string | | Título del adjunto |
| `subtitle` | string | No | Subtítulo/descripción del adjunto |
#### Salida
@@ -673,7 +699,7 @@ Actualizar los metadatos de un adjunto en Linear
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | ----------- | ----------- |
| `attachmentId` | string | Sí | ID del adjunto a actualizar |
| `title` | string | No | Nuevo título del adjunto |
| `title` | string | | Nuevo título del adjunto |
| `subtitle` | string | No | Nuevo subtítulo del adjunto |
#### Salida
@@ -707,8 +733,8 @@ Vincular dos incidencias en Linear (bloquea, se relaciona con, duplica)
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | ----------- | ----------- |
| `issueId` | string | Sí | ID de la incidencia de origen |
| `relatedIssueId` | string | Sí | ID de la incidencia de destino a vincular |
| `type` | string | Sí | Tipo de relación: "blocks", "blocked", "duplicate", "related" |
| `relatedIssueId` | string | Sí | ID de la incidencia de destino a la que vincular |
| `type` | string | Sí | Tipo de relación: "blocks", "duplicate", o "related". Nota: Al crear "blocks" de A a B, la relación inversa \(B bloqueada por A\) se crea automáticamente. |
#### Salida
@@ -1217,10 +1243,11 @@ Crear una nueva etiqueta de proyecto en Linear
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | ----------- | ----------- |
| `name` | string | Sí | Nombre de la etiqueta de proyecto |
| `color` | string | No | Color de la etiqueta (código hexadecimal) |
| `projectId` | string | Sí | El proyecto para esta etiqueta |
| `name` | string | | Nombre de la etiqueta del proyecto |
| `color` | string | No | Color de la etiqueta \(código hex\) |
| `description` | string | No | Descripción de la etiqueta |
| `isGroup` | boolean | No | Indica si es un grupo de etiquetas |
| `isGroup` | boolean | No | Indica si esta es un grupo de etiquetas |
| `parentId` | string | No | ID del grupo de etiquetas padre |
#### Salida
@@ -1394,10 +1421,11 @@ Crear un nuevo estado de proyecto en Linear
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | ----------- | ----------- |
| `projectId` | string | Sí | El proyecto para el que crear el estado |
| `name` | string | Sí | Nombre del estado del proyecto |
| `color` | string | Sí | Color del estado \(código hex\) |
| `color` | string | Sí | Color del estado (código hex) |
| `description` | string | No | Descripción del estado |
| `indefinite` | boolean | No | Indica si el estado es indefinido |
| `indefinite` | boolean | No | Si el estado es indefinido |
| `position` | number | No | Posición en la lista de estados |
#### Salida

View File

@@ -39,14 +39,24 @@ Récupérer et filtrer les tickets depuis Linear
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| `teamId` | chaîne | Oui | ID de l'équipe Linear |
| `projectId` | chaîne | Oui | ID du projet Linear |
| `teamId` | chaîne | Non | ID de l'équipe Linear pour filtrer |
| `projectId` | chaîne | Non | ID du projet Linear pour filtrer |
| `assigneeId` | chaîne | Non | ID de l'utilisateur pour filtrer par assigné |
| `stateId` | chaîne | Non | ID de l'état du flux de travail pour filtrer par statut |
| `priority` | nombre | Non | Priorité pour filtrer \(0=Pas de priorité, 1=Urgent, 2=Élevée, 3=Normale, 4=Faible\) |
| `labelIds` | tableau | Non | Tableau des IDs d'étiquettes pour filtrer |
| `createdAfter` | chaîne | Non | Filtrer les tickets créés après cette date \(format ISO 8601\) |
| `updatedAfter` | chaîne | Non | Filtrer les tickets mis à jour après cette date \(format ISO 8601\) |
| `includeArchived` | booléen | Non | Inclure les tickets archivés \(par défaut : false\) |
| `first` | nombre | Non | Nombre de tickets à retourner \(par défaut : 50, max : 250\) |
| `after` | chaîne | Non | Curseur de pagination pour la page suivante |
| `orderBy` | chaîne | Non | Ordre de tri : "createdAt" ou "updatedAt" \(par défaut : "updatedAt"\) |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `issues` | tableau | Tableau des tickets du projet et de l'équipe Linear spécifiés, chacun contenant id, titre, description, état, teamId et projectId |
| `issues` | tableau | Tableau des tickets filtrés de Linear |
### `linear_get_issue`
@@ -73,15 +83,25 @@ Créer un nouveau ticket dans Linear
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| `teamId` | chaîne | Oui | ID de l'équipe Linear |
| `projectId` | chaîne | Oui | ID du projet Linear |
| `projectId` | chaîne | Non | ID du projet Linear |
| `title` | chaîne | Oui | Titre du ticket |
| `description` | chaîne | Non | Description du ticket |
| `stateId` | chaîne | Non | ID de l'état du flux de travail \(statut\) |
| `assigneeId` | chaîne | Non | ID de l'utilisateur à qui assigner le ticket |
| `priority` | nombre | Non | Priorité \(0=Pas de priorité, 1=Urgent, 2=Élevée, 3=Normale, 4=Faible\) |
| `estimate` | nombre | Non | Estimation en points |
| `labelIds` | tableau | Non | Tableau des IDs d'étiquettes à définir sur le ticket |
| `cycleId` | chaîne | Non | ID du cycle auquel assigner le ticket |
| `parentId` | chaîne | Non | ID du ticket parent \(pour créer des sous-tickets\) |
| `dueDate` | chaîne | Non | Date d'échéance au format ISO 8601 \(date uniquement : AAAA-MM-JJ\) |
| `subscriberIds` | tableau | Non | Tableau des IDs d'utilisateurs à abonner au ticket |
| `projectMilestoneId` | chaîne | Non | ID de l'étape clé du projet à associer au ticket |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `issue` | objet | Le ticket créé contenant id, titre, description, état, teamId et projectId |
| `issue` | objet | Le ticket créé avec toutes ses propriétés |
### `linear_update_issue`
@@ -94,11 +114,17 @@ Mettre à jour un ticket existant dans Linear
| `issueId` | chaîne | Oui | ID du ticket Linear à mettre à jour |
| `title` | chaîne | Non | Nouveau titre du ticket |
| `description` | chaîne | Non | Nouvelle description du ticket |
| `stateId` | chaîne | Non | ID de l'état du flux de travail \(statut\) |
| `stateId` | chaîne | Non | ID de l'état du workflow \(statut\) |
| `assigneeId` | chaîne | Non | ID de l'utilisateur à qui assigner le ticket |
| `priority` | nombre | Non | Priorité \(0=Pas de priorité, 1=Urgent, 2=Élevée, 3=Normale, 4=Faible\) |
| `priority` | nombre | Non | Priorité \(0=Aucune priorité, 1=Urgent, 2=Élevée, 3=Normale, 4=Faible\) |
| `estimate` | nombre | Non | Estimation en points |
| `labelIds` | tableau | Non | Tableau des IDs d'étiquettes à définir sur le ticket |
| `labelIds` | tableau | Non | Tableau des ID d'étiquettes à définir sur le ticket \(remplace toutes les étiquettes existantes\) |
| `projectId` | chaîne | Non | ID du projet vers lequel déplacer le ticket |
| `cycleId` | chaîne | Non | ID du cycle auquel assigner le ticket |
| `parentId` | chaîne | Non | ID du ticket parent \(pour en faire un sous-ticket\) |
| `dueDate` | chaîne | Non | Date d'échéance au format ISO 8601 \(date uniquement : AAAA-MM-JJ\) |
| `addedLabelIds` | tableau | Non | Tableau des ID d'étiquettes à ajouter au ticket \(sans remplacer les étiquettes existantes\) |
| `removedLabelIds` | tableau | Non | Tableau des ID d'étiquettes à supprimer du ticket |
#### Sortie
@@ -237,7 +263,7 @@ Modifier un commentaire dans Linear
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| `commentId` | chaîne | Oui | ID du commentaire à mettre à jour |
| `body` | chaîne | Oui | Nouveau texte du commentaire \(supporte le Markdown\) |
| `body` | chaîne | Non | Nouveau texte du commentaire \(supporte le Markdown\) |
#### Sortie
@@ -349,9 +375,9 @@ Mettre à jour un projet existant dans Linear
| `description` | chaîne | Non | Nouvelle description du projet |
| `state` | chaîne | Non | État du projet \(planned, started, completed, canceled\) |
| `leadId` | chaîne | Non | ID de l'utilisateur responsable du projet |
| `startDate` | chaîne | Non | Date de début du projet \(format ISO\) |
| `targetDate` | chaîne | Non | Date cible du projet \(format ISO\) |
| `priority` | nombre | Non | Priorité du projet \(0-4\) |
| `startDate` | chaîne | Non | Date de début du projet \(format ISO : YYYY-MM-DD\) |
| `targetDate` | chaîne | Non | Date cible du projet \(format ISO : YYYY-MM-DD\) |
| `priority` | nombre | Non | Priorité du projet \(0=Pas de priorité, 1=Urgent, 2=Élevée, 3=Normale, 4=Faible\) |
#### Sortie
@@ -526,11 +552,11 @@ Créer un nouvel état de workflow (statut) dans Linear
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| `teamId` | chaîne | Oui | ID de l'équipe dans laquelle créer l'état |
| `name` | chaîne | Oui | Nom de l'état \(ex., "En révision"\) |
| `color` | chaîne | Oui | Couleur de l'état \(format hexadécimal\) |
| `name` | chaîne | Oui | Nom de l'état \(ex. : "En révision"\) |
| `color` | chaîne | Non | Couleur de l'état \(format hexadécimal\) |
| `type` | chaîne | Oui | Type d'état : "backlog", "unstarted", "started", "completed", ou "canceled" |
| `description` | chaîne | Non | Description de l'état |
| `position` | nombre | Non | Position dans le workflow |
| `position` | nombre | Non | Position dans le flux de travail |
#### Sortie
@@ -635,9 +661,9 @@ Ajouter une pièce jointe à un ticket dans Linear
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| `issueId` | chaîne | Oui | ID du ticket auquel joindre la pièce |
| `issueId` | chaîne | Oui | ID du ticket auquel joindre |
| `url` | chaîne | Oui | URL de la pièce jointe |
| `title` | chaîne | Non | Titre de la pièce jointe |
| `title` | chaîne | Oui | Titre de la pièce jointe |
| `subtitle` | chaîne | Non | Sous-titre/description de la pièce jointe |
#### Sortie
@@ -673,7 +699,7 @@ Mettre à jour les métadonnées d'une pièce jointe dans Linear
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| `attachmentId` | chaîne | Oui | ID de la pièce jointe à mettre à jour |
| `title` | chaîne | Non | Nouveau titre de la pièce jointe |
| `title` | chaîne | Oui | Nouveau titre de la pièce jointe |
| `subtitle` | chaîne | Non | Nouveau sous-titre de la pièce jointe |
#### Sortie
@@ -708,7 +734,7 @@ Lier deux tickets ensemble dans Linear (bloque, est lié à, duplique)
| --------- | ---- | ----------- | ----------- |
| `issueId` | chaîne | Oui | ID du ticket source |
| `relatedIssueId` | chaîne | Oui | ID du ticket cible à lier |
| `type` | chaîne | Oui | Type de relation : "blocks", "blocked", "duplicate", "related" |
| `type` | chaîne | Oui | Type de relation : "blocks", "duplicate", ou "related". Remarque : Lors de la création d'une relation "blocks" de A vers B, la relation inverse \(B bloqué par A\) est automatiquement créée. |
#### Sortie
@@ -1217,6 +1243,7 @@ Créer une nouvelle étiquette de projet dans Linear
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| `projectId` | chaîne | Oui | Le projet pour cette étiquette |
| `name` | chaîne | Oui | Nom de l'étiquette de projet |
| `color` | chaîne | Non | Couleur de l'étiquette \(code hexadécimal\) |
| `description` | chaîne | Non | Description de l'étiquette |
@@ -1394,7 +1421,8 @@ Créer un nouveau statut de projet dans Linear
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| `name` | chaîne | Oui | Nom du statut du projet |
| `projectId` | chaîne | Oui | Le projet pour lequel créer le statut |
| `name` | chaîne | Oui | Nom du statut de projet |
| `color` | chaîne | Oui | Couleur du statut \(code hexadécimal\) |
| `description` | chaîne | Non | Description du statut |
| `indefinite` | booléen | Non | Indique si le statut est indéfini |

View File

@@ -39,14 +39,24 @@ Linearから課題を取得してフィルタリングする
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `teamId` | string | はい | LinearチームID |
| `projectId` | string | はい | LinearプロジェクトID |
| `teamId` | string | いいえ | フィルタリングするLinearチームID |
| `projectId` | string | いいえ | フィルタリングするLinearプロジェクトID |
| `assigneeId` | string | いいえ | 担当者でフィルタリングするユーザーID |
| `stateId` | string | いいえ | ステータスでフィルタリングするワークフロー状態ID |
| `priority` | number | いいえ | フィルタリングする優先度0=優先度なし、1=緊急、2=高、3=普通、4=低) |
| `labelIds` | array | いいえ | フィルタリングするラベルIDの配列 |
| `createdAfter` | string | いいえ | この日付以降に作成された課題をフィルタリングISO 8601形式 |
| `updatedAfter` | string | いいえ | この日付以降に更新された課題をフィルタリングISO 8601形式 |
| `includeArchived` | boolean | いいえ | アーカイブされた課題を含めるデフォルトfalse |
| `first` | number | いいえ | 返す課題の数デフォルト50、最大250 |
| `after` | string | いいえ | 次のページのページネーションカーソル |
| `orderBy` | string | いいえ | ソート順:"createdAt"または"updatedAt"(デフォルト:"updatedAt" |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `issues` | array | 指定されたLinearチームとプロジェクトからの課題の配列。各課題にはid、title、description、state、teamId、projectIdが含まれます |
| `issues` | array | Linearからフィルタリングされた課題の配列 |
### `linear_get_issue`
@@ -73,15 +83,25 @@ Linearに新しい課題を作成する
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `teamId` | string | はい | LinearチームID |
| `projectId` | string | い | LinearプロジェクトID |
| `projectId` | string | いいえ | LinearプロジェクトID |
| `title` | string | はい | 課題のタイトル |
| `description` | string | いいえ | 課題の説明 |
| `stateId` | string | いいえ | ワークフロー状態IDステータス |
| `assigneeId` | string | いいえ | 課題を割り当てるユーザーID |
| `priority` | number | いいえ | 優先度0=優先度なし、1=緊急、2=高、3=普通、4=低) |
| `estimate` | number | いいえ | ポイント単位の見積もり |
| `labelIds` | array | いいえ | 課題に設定するラベルIDの配列 |
| `cycleId` | string | いいえ | 課題を割り当てるサイクルID |
| `parentId` | string | いいえ | 親課題IDサブ課題を作成する場合 |
| `dueDate` | string | いいえ | ISO 8601形式の期日日付のみYYYY-MM-DD |
| `subscriberIds` | array | いいえ | 課題をサブスクライブするユーザーIDの配列 |
| `projectMilestoneId` | string | いいえ | 課題に関連付けるプロジェクトマイルストーンID |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `issue` | object | 作成された課題id、title、description、state、teamId、projectIdを含む |
| `issue` | object | すべてのプロパティを含む作成された課題 |
### `linear_update_issue`
@@ -97,8 +117,14 @@ Linearの既存の課題を更新する
| `stateId` | string | いいえ | ワークフロー状態IDステータス |
| `assigneeId` | string | いいえ | 課題を割り当てるユーザーID |
| `priority` | number | いいえ | 優先度0=優先度なし、1=緊急、2=高、3=普通、4=低) |
| `estimate` | number | いいえ | ポイント単位の見積もり |
| `labelIds` | array | いいえ | 課題に設定するラベルIDの配列 |
| `estimate` | number | いいえ | ポイントの見積もり |
| `labelIds` | array | いいえ | 課題に設定するラベルIDの配列(既存のすべてのラベルを置き換え) |
| `projectId` | string | いいえ | 課題を移動するプロジェクトID |
| `cycleId` | string | いいえ | 課題を割り当てるサイクルID |
| `parentId` | string | いいえ | 親課題IDこれをサブ課題にする場合 |
| `dueDate` | string | いいえ | ISO 8601形式の期日日付のみYYYY-MM-DD |
| `addedLabelIds` | array | いいえ | 課題に追加するラベルIDの配列既存のラベルを置き換えない |
| `removedLabelIds` | array | いいえ | 課題から削除するラベルIDの配列 |
#### 出力
@@ -237,7 +263,7 @@ Linearでコメントを編集する
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `commentId` | string | はい | 更新するコメントID |
| `body` | string | い | 新しいコメントテキストMarkdownをサポート |
| `body` | string | いいえ | 新しいコメントテキストMarkdownをサポート |
#### 出力
@@ -349,9 +375,9 @@ Linearの既存プロジェクトを更新する
| `description` | string | いいえ | 新しいプロジェクトの説明 |
| `state` | string | いいえ | プロジェクトの状態(計画中、開始済み、完了、キャンセル) |
| `leadId` | string | いいえ | プロジェクトリーダーのユーザーID |
| `startDate` | string | いいえ | プロジェクト開始日ISO形式 |
| `targetDate` | string | いいえ | プロジェクト目標日ISO形式 |
| `priority` | number | いいえ | プロジェクト優先度0-4 |
| `startDate` | string | いいえ | プロジェクト開始日ISO形式YYYY-MM-DD |
| `targetDate` | string | いいえ | プロジェクト目標日ISO形式YYYY-MM-DD |
| `priority` | number | いいえ | プロジェクト優先度0=優先度なし、1=緊急、2=高、3=普通、4=低 |
#### 出力
@@ -527,7 +553,7 @@ Linearに新しいワークフローステータス状態を作成する
| --------- | ---- | -------- | ----------- |
| `teamId` | string | はい | ステータスを作成するチームID |
| `name` | string | はい | ステータス名(例:「レビュー中」) |
| `color` | string | い | ステータスの色16進形式 |
| `color` | string | いいえ | ステータスの色16進形式 |
| `type` | string | はい | ステータスタイプ「backlog」、「unstarted」、「started」、「completed」、または「canceled」 |
| `description` | string | いいえ | ステータスの説明 |
| `position` | number | いいえ | ワークフロー内の位置 |
@@ -635,9 +661,9 @@ Linearの課題に添付ファイルを追加する
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `issueId` | string | はい | 添付先の課題ID |
| `issueId` | string | はい | 添付する課題ID |
| `url` | string | はい | 添付ファイルのURL |
| `title` | string | いいえ | 添付ファイルのタイトル |
| `title` | string | い | 添付ファイルのタイトル |
| `subtitle` | string | いいえ | 添付ファイルのサブタイトル/説明 |
#### 出力
@@ -673,7 +699,7 @@ Linearで添付ファイルのメタデータを更新する
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `attachmentId` | string | はい | 更新する添付ファイルID |
| `title` | string | いいえ | 新しい添付ファイルのタイトル |
| `title` | string | い | 新しい添付ファイルのタイトル |
| `subtitle` | string | いいえ | 新しい添付ファイルのサブタイトル |
#### 出力
@@ -708,7 +734,7 @@ Linearで2つの課題を関連付けるブロック、関連、重複
| --------- | ---- | -------- | ----------- |
| `issueId` | string | はい | ソース課題ID |
| `relatedIssueId` | string | はい | リンク先のターゲット課題ID |
| `type` | string | はい | 関係タイプ:blocks」、「blocked」、「duplicate」、「related |
| `type` | string | はい | 関係タイプ:"blocks"、"duplicate"、または"related"。注意AからBへの"blocks"を作成すると、逆の関係BはAによってブロックされるが自動的に作成されます。 |
#### 出力
@@ -1217,6 +1243,7 @@ Linearで新しいプロジェクトラベルを作成する
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | はい | このラベルのプロジェクト |
| `name` | string | はい | プロジェクトラベル名 |
| `color` | string | いいえ | ラベルの色16進コード |
| `description` | string | いいえ | ラベルの説明 |
@@ -1394,6 +1421,7 @@ Linearで新しいプロジェクトステータスを作成する
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | はい | ステータスを作成するプロジェクト |
| `name` | string | はい | プロジェクトステータス名 |
| `color` | string | はい | ステータスの色16進コード |
| `description` | string | いいえ | ステータスの説明 |

View File

@@ -39,14 +39,24 @@ Linear 的主要功能包括:
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `teamId` | string | | Linear 团队 ID |
| `projectId` | string | | Linear 项目 ID |
| `teamId` | string | | Linear 团队 ID 筛选 |
| `projectId` | string | | Linear 项目 ID 筛选 |
| `assigneeId` | string | 否 | 按分配的用户 ID 筛选 |
| `stateId` | string | 否 | 按工作流状态 ID 筛选(状态)|
| `priority` | number | 否 | 按优先级筛选0=无优先级1=紧急2=高3=正常4=低)|
| `labelIds` | array | 否 | 按标签 ID 数组筛选 |
| `createdAfter` | string | 否 | 筛选创建日期晚于此日期的问题ISO 8601 格式)|
| `updatedAfter` | string | 否 | 筛选更新日期晚于此日期的问题ISO 8601 格式)|
| `includeArchived` | boolean | 否 | 包括已归档的问题默认值false|
| `first` | number | 否 | 返回的问题数量默认值50最大值250|
| `after` | string | 否 | 下一页的分页游标 |
| `orderBy` | string | 否 | 排序顺序:"createdAt" 或 "updatedAt"(默认值:"updatedAt"|
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `issues` | array | 来自指定 Linear 团队和项目的问题数组,每个问题包含 id、title、description、state、teamId 和 projectId |
| `issues` | array | Linear 筛选的问题数组 |
### `linear_get_issue`
@@ -73,15 +83,25 @@ Linear 的主要功能包括:
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `teamId` | string | 是 | Linear 团队 ID |
| `projectId` | string | | Linear 项目 ID |
| `projectId` | string | | Linear 项目 ID |
| `title` | string | 是 | 问题标题 |
| `description` | string | 否 | 问题描述 |
| `stateId` | string | 否 | 工作流状态 ID状态|
| `assigneeId` | string | 否 | 要分配问题的用户 ID |
| `priority` | number | 否 | 优先级0=无优先级1=紧急2=高3=正常4=低)|
| `estimate` | number | 否 | 以点数估算 |
| `labelIds` | array | 否 | 要设置在问题上的标签 ID 数组 |
| `cycleId` | string | 否 | 要分配问题的周期 ID |
| `parentId` | string | 否 | 父问题 ID用于创建子问题|
| `dueDate` | string | 否 | 到期日期ISO 8601 格式仅日期YYYY-MM-DD|
| `subscriberIds` | array | 否 | 要订阅问题的用户 ID 数组 |
| `projectMilestoneId` | string | 否 | 要与问题关联的项目里程碑 ID |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `issue` | object | 创建的问题,包含 id、title、description、state、teamId 和 projectId |
| `issue` | object | 创建的包含所有属性的问题 |
### `linear_update_issue`
@@ -94,11 +114,17 @@ Linear 的主要功能包括:
| `issueId` | string | 是 | 要更新的 Linear 问题 ID |
| `title` | string | 否 | 新的问题标题 |
| `description` | string | 否 | 新的问题描述 |
| `stateId` | string | 否 | 工作流状态 ID状态 |
| `assigneeId` | string | 否 | 分配问题的用户 ID |
| `priority` | number | 否 | 优先级0=无优先级1=紧急2=高3=正常4=低) |
| `estimate` | number | 否 | 以点数估算 |
| `labelIds` | array | 否 | 设置在问题上的标签 ID 数组 |
| `stateId` | string | 否 | 工作流状态 ID状态|
| `assigneeId` | string | 否 | 分配问题的用户 ID |
| `priority` | number | 否 | 优先级0=无优先级1=紧急2=高3=正常4=低)|
| `estimate` | number | 否 | 估算点数 |
| `labelIds` | array | 否 | 设置在问题上的标签 ID 数组(替换所有现有标签)|
| `projectId` | string | 否 | 要移动问题到的项目 ID |
| `cycleId` | string | 否 | 分配给问题的周期 ID |
| `parentId` | string | 否 | 父问题 ID将其设为子问题|
| `dueDate` | string | 否 | ISO 8601 格式的截止日期仅日期YYYY-MM-DD|
| `addedLabelIds` | array | 否 | 添加到问题的标签 ID 数组(不替换现有标签)|
| `removedLabelIds` | array | 否 | 从问题中移除的标签 ID 数组 |
#### 输出
@@ -237,7 +263,7 @@ Linear 的主要功能包括:
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `commentId` | string | 是 | 要更新的评论 ID |
| `body` | string | | 新的评论文本(支持 Markdown |
| `body` | string | | 新的评论文本(支持 Markdown |
#### 输出
@@ -347,11 +373,11 @@ Linear 的主要功能包括:
| `projectId` | string | 是 | 要更新的项目 ID |
| `name` | string | 否 | 新的项目名称 |
| `description` | string | 否 | 新的项目描述 |
| `state` | string | 否 | 项目状态(计划中、已开始、已完成、已取消)|
| `state` | string | 否 | 项目状态(planned、started、completed、canceled |
| `leadId` | string | 否 | 项目负责人的用户 ID |
| `startDate` | string | 否 | 项目开始日期ISO 格式)|
| `targetDate` | string | 否 | 项目目标日期ISO 格式)|
| `priority` | number | 否 | 项目优先级0-4|
| `startDate` | string | 否 | 项目开始日期ISO 格式: YYYY-MM-DD|
| `targetDate` | string | 否 | 项目目标日期ISO 格式: YYYY-MM-DD|
| `priority` | number | 否 | 项目优先级0=无优先级1=紧急2=高3=正常4=低) |
#### 输出
@@ -525,10 +551,10 @@ Linear 的主要功能包括:
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `teamId` | string | 是 | 要创建状态的团队 ID |
| `name` | string | 是 | 状态名称 \(例如"In Review"\) |
| `color` | string | | 状态颜色 \(十六进制格式\) |
| `type` | string | 是 | 状态类型"backlog"、"unstarted"、"started"、"completed" 或 "canceled" |
| `teamId` | string | 是 | 要在其中创建状态的团队 ID |
| `name` | string | 是 | 状态名称例如 "In Review"|
| `color` | string | | 状态颜色十六进制格式|
| `type` | string | 是 | 状态类型: "backlog"、"unstarted"、"started"、"completed" 或 "canceled" |
| `description` | string | 否 | 状态描述 |
| `position` | number | 否 | 工作流中的位置 |
@@ -637,7 +663,7 @@ Linear 的主要功能包括:
| --------- | ---- | -------- | ----------- |
| `issueId` | string | 是 | 要附加的 Issue ID |
| `url` | string | 是 | 附件的 URL |
| `title` | string | | 附件标题 |
| `title` | string | | 附件标题 |
| `subtitle` | string | 否 | 附件副标题/描述 |
#### 输出
@@ -673,7 +699,7 @@ Linear 的主要功能包括:
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `attachmentId` | string | 是 | 要更新的附件 ID |
| `title` | string | | 新的附件标题 |
| `title` | string | | 新的附件标题 |
| `subtitle` | string | 否 | 新的附件副标题 |
#### 输出
@@ -708,7 +734,7 @@ Linear 的主要功能包括:
| --------- | ---- | -------- | ----------- |
| `issueId` | string | 是 | 源问题 ID |
| `relatedIssueId` | string | 是 | 要链接的目标问题 ID |
| `type` | string | 是 | 关系类型"blocks"(阻止)、"blocked"(被阻止)、"duplicate"(重复)、"related"(相关) |
| `type` | string | 是 | 关系类型: "blocks"、"duplicate""related"。注意:当从 A 到 B 创建 "blocks" 时,会自动创建反向关系 \(B 被 A 阻止\)。 |
#### 输出
@@ -1217,6 +1243,7 @@ Linear 的主要功能包括:
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | 是 | 此标签所属的项目 |
| `name` | string | 是 | 项目标签名称 |
| `color` | string | 否 | 标签颜色 \(十六进制代码\) |
| `description` | string | 否 | 标签描述 |
@@ -1394,6 +1421,7 @@ Linear 的主要功能包括:
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | 是 | 要为其创建状态的项目 |
| `name` | string | 是 | 项目状态名称 |
| `color` | string | 是 | 状态颜色 \(十六进制代码\) |
| `description` | string | 否 | 状态描述 |

View File

@@ -1964,9 +1964,9 @@ checksums:
content/10: dc0cd9ecdd8caafc0a4a7c60b68daa2f
content/11: 67ffd9a9b55ad6c1259af98deafb5d0a
content/12: 371d0e46b4bd2c23f559b8bc112f6955
content/13: 1b7e0cec71c2ad722e356ba1f569da6b
content/13: 8b8b040a8485fabfa0c94482163fc39c
content/14: bcadfc362b69078beee0088e5936c98b
content/15: b6cfb067664d66c5793de41ace6a3ef3
content/15: bbc717461139df90f307b1e5715978bf
content/16: 29a606473b5216cceb515b5a8b433f63
content/17: 731e123330335c611fcbf70919e955b9
content/18: 371d0e46b4bd2c23f559b8bc112f6955
@@ -1976,13 +1976,13 @@ checksums:
content/22: e4b5d2b30f9a5ac7456d9e2403ff6fc1
content/23: ba6b661f58f6c55fceb6666247b185be
content/24: 371d0e46b4bd2c23f559b8bc112f6955
content/25: 1813df0e019589847b73769342f51bc3
content/25: 8373a4b5ffe81061f22326e0f8e9f84e
content/26: bcadfc362b69078beee0088e5936c98b
content/27: d64618728def8031137bc39f9c14072b
content/27: 6e88d2c0bd7f3563d0d024c74855361b
content/28: 4240d9c02238325392a6c56fe55587ec
content/29: 456d6e5be5f40d6290b6783b638e316b
content/30: 371d0e46b4bd2c23f559b8bc112f6955
content/31: e292bf642654431b8795f9559c07f330
content/31: 0a5e9e47873a22622a9ab035199df6d2
content/32: bcadfc362b69078beee0088e5936c98b
content/33: e13d950ad68beff6352319bb0d102fec
content/34: 0b78c3741f5caf86271cd3fcad674785
@@ -2030,7 +2030,7 @@ checksums:
content/76: 2ce20e0b75472e89c4ad0e5bbb588bea
content/77: 1e6015e207ad5512fe81843588b1c8a7
content/78: 371d0e46b4bd2c23f559b8bc112f6955
content/79: a3c0aad623f2b68eb7caa312031855e0
content/79: 9023fec7d6c0d587d0de217d8ec3145c
content/80: bcadfc362b69078beee0088e5936c98b
content/81: 3f738270799daa8b08c67b1e1a8fc6ce
content/82: 5a2a4e06bf108686b2eade2cda5a0213
@@ -2066,7 +2066,7 @@ checksums:
content/112: 36b1e6fea4549c6b0f3c93fb51d0baa6
content/113: e01cc332d94f7aeda13d63e9e5159498
content/114: 371d0e46b4bd2c23f559b8bc112f6955
content/115: 1066dc3f18676022fc28ad2ac3dbdcbe
content/115: ce33b88492725c3912f8cc46d7959e92
content/116: bcadfc362b69078beee0088e5936c98b
content/117: 0b7ea576dff35049039f27e93f492baa
content/118: e72ce72b4ba59689765e1520bebd673d
@@ -2126,7 +2126,7 @@ checksums:
content/172: 6ac5231c6f1e4af355f4a8e9cc131c8c
content/173: c4e86be3717bab3a162af6fd7d07d40f
content/174: 371d0e46b4bd2c23f559b8bc112f6955
content/175: f875917d0ddc6c49724cb05d6dbc20d3
content/175: a6bb9e81a93241a9f5f4f0c54a8e158f
content/176: bcadfc362b69078beee0088e5936c98b
content/177: 4bd7f106ee69c2b18cb2a41ce8a45c49
content/178: dfdf7e15f125ed33d381182421afd071
@@ -2162,7 +2162,7 @@ checksums:
content/208: 289b4c3ba8ddf2b116b89e8f42c42f7a
content/209: 70f307005f7268b098c2666493f83760
content/210: 371d0e46b4bd2c23f559b8bc112f6955
content/211: 2ad4046ebf806f5832618a982f3e5759
content/211: 2d3e84b61c7050a6278ee34ea1b6a30d
content/212: bcadfc362b69078beee0088e5936c98b
content/213: 00240f5f806fa8ce1cd54e332befbcf4
content/214: 5905f216a56867439934135577706de6
@@ -2174,7 +2174,7 @@ checksums:
content/220: 3a75b78856cc47e4fda093d997f74b3d
content/221: 658f7e9229c459b86172cd017c239e80
content/222: 371d0e46b4bd2c23f559b8bc112f6955
content/223: 44e98a1518d5fc5b56ffba994cda9c7f
content/223: 2236e1a16509d71cbec735d45e1f8982
content/224: bcadfc362b69078beee0088e5936c98b
content/225: 1ee2ba1b12abc61e709857685e82b029
content/226: 7bd180fada856c4e3acf6f4aa2dbd866
@@ -2186,7 +2186,7 @@ checksums:
content/232: eec28b7a4994e560ebfe7b65eb9489f5
content/233: e686dab5089c0869f094700f53492a4d
content/234: 371d0e46b4bd2c23f559b8bc112f6955
content/235: 0ac1e8cb148c61dc238236c47904da52
content/235: 9c6e8834385fa1a104f2a49561e19e8b
content/236: bcadfc362b69078beee0088e5936c98b
content/237: 81f1f23936bbd1dc4b21bf71e84bd28c
content/238: e93d92ffdc4d1d66b5100773399d93c0
@@ -2354,7 +2354,7 @@ checksums:
content/400: f17b5c16f106085925c714b22959cc0f
content/401: c50d1424ef13db47c44f10dfee39a920
content/402: 371d0e46b4bd2c23f559b8bc112f6955
content/403: 4fb3b6ac2f4416abd838807e4f0efb68
content/403: 587400a0f1fd869064c9ce3f9e1ec394
content/404: bcadfc362b69078beee0088e5936c98b
content/405: 48a91e9804204fb902733bbcb519d8f3
content/406: 417531850860835ff8d1bc4990576f8a
@@ -2414,7 +2414,7 @@ checksums:
content/460: 4278f1ea3ef8e4e141aa55455dc45b7f
content/461: 2563a5d8d8c790ce109d706ce3443807
content/462: 371d0e46b4bd2c23f559b8bc112f6955
content/463: b12446be8e7501c8ad4a32634c07cd0a
content/463: 8a4f40f409c0eb1cf8b23a2f3f369c09
content/464: bcadfc362b69078beee0088e5936c98b
content/465: eaff49649d1ae705346a12e04ea3b741
content/466: b5d7ba66d1bfee635d2b79d1069d542b

View File

@@ -1,5 +1,5 @@
import { cn } from '@/lib/utils'
import AuthBackgroundSVG from './auth-background-svg'
import AuthBackgroundSVG from '@/app/(auth)/components/auth-background-svg'
type AuthBackgroundProps = {
className?: string

View File

@@ -4,7 +4,7 @@ import { type ReactNode, useEffect, useState } from 'react'
import { GithubIcon, GoogleIcon } from '@/components/icons'
import { Button } from '@/components/ui/button'
import { client } from '@/lib/auth-client'
import { inter } from '@/app/fonts/inter/inter'
import { inter } from '@/app/_styles/fonts/inter/inter'
interface SocialLoginButtonsProps {
githubAvailable: boolean

View File

@@ -1,8 +1,8 @@
'use client'
import { useEffect } from 'react'
import AuthBackground from '@/app/(auth)/components/auth-background'
import Nav from '@/app/(landing)/components/nav/nav'
import AuthBackground from './components/auth-background'
// Helper to detect if a color is dark
function isColorDark(hexColor: string): boolean {

View File

@@ -20,10 +20,10 @@ import { getEnv, isFalsy, isTruthy } from '@/lib/env'
import { createLogger } from '@/lib/logs/console/logger'
import { getBaseUrl } from '@/lib/urls/utils'
import { cn } from '@/lib/utils'
import { inter } from '@/app/_styles/fonts/inter/inter'
import { soehne } from '@/app/_styles/fonts/soehne/soehne'
import { SocialLoginButtons } from '@/app/(auth)/components/social-login-buttons'
import { SSOLoginButton } from '@/app/(auth)/components/sso-login-button'
import { inter } from '@/app/fonts/inter/inter'
import { soehne } from '@/app/fonts/soehne/soehne'
const logger = createLogger('LoginForm')

View File

@@ -4,9 +4,9 @@ import { Suspense, useEffect, useState } from 'react'
import Link from 'next/link'
import { useRouter, useSearchParams } from 'next/navigation'
import { createLogger } from '@/lib/logs/console/logger'
import { inter } from '@/app/_styles/fonts/inter/inter'
import { soehne } from '@/app/_styles/fonts/soehne/soehne'
import { SetNewPasswordForm } from '@/app/(auth)/reset-password/reset-password-form'
import { inter } from '@/app/fonts/inter/inter'
import { soehne } from '@/app/fonts/soehne/soehne'
const logger = createLogger('ResetPasswordPage')

View File

@@ -6,7 +6,7 @@ import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { cn } from '@/lib/utils'
import { inter } from '@/app/fonts/inter/inter'
import { inter } from '@/app/_styles/fonts/inter/inter'
interface RequestResetFormProps {
email: string

View File

@@ -12,10 +12,10 @@ import { quickValidateEmail } from '@/lib/email/validation'
import { getEnv, isFalsy, isTruthy } from '@/lib/env'
import { createLogger } from '@/lib/logs/console/logger'
import { cn } from '@/lib/utils'
import { inter } from '@/app/_styles/fonts/inter/inter'
import { soehne } from '@/app/_styles/fonts/soehne/soehne'
import { SocialLoginButtons } from '@/app/(auth)/components/social-login-buttons'
import { SSOLoginButton } from '@/app/(auth)/components/sso-login-button'
import { inter } from '@/app/fonts/inter/inter'
import { soehne } from '@/app/fonts/soehne/soehne'
const logger = createLogger('SignupForm')

View File

@@ -1,6 +1,6 @@
import { redirect } from 'next/navigation'
import { getEnv, isTruthy } from '@/lib/env'
import SSOForm from './sso-form'
import SSOForm from '@/app/(auth)/sso/sso-form'
export const dynamic = 'force-dynamic'

View File

@@ -11,8 +11,8 @@ import { quickValidateEmail } from '@/lib/email/validation'
import { env, isFalsy } from '@/lib/env'
import { createLogger } from '@/lib/logs/console/logger'
import { cn } from '@/lib/utils'
import { inter } from '@/app/fonts/inter/inter'
import { soehne } from '@/app/fonts/soehne/soehne'
import { inter } from '@/app/_styles/fonts/inter/inter'
import { soehne } from '@/app/_styles/fonts/soehne/soehne'
const logger = createLogger('SSOForm')

View File

@@ -5,9 +5,9 @@ import { useRouter } from 'next/navigation'
import { Button } from '@/components/ui/button'
import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/input-otp'
import { cn } from '@/lib/utils'
import { inter } from '@/app/_styles/fonts/inter/inter'
import { soehne } from '@/app/_styles/fonts/soehne/soehne'
import { useVerification } from '@/app/(auth)/verify/use-verification'
import { inter } from '@/app/fonts/inter/inter'
import { soehne } from '@/app/fonts/soehne/soehne'
interface VerifyContentProps {
hasEmailService: boolean

View File

@@ -17,9 +17,9 @@ import { quickValidateEmail } from '@/lib/email/validation'
import { isHosted } from '@/lib/environment'
import { createLogger } from '@/lib/logs/console/logger'
import { cn } from '@/lib/utils'
import { soehne } from '@/app/_styles/fonts/soehne/soehne'
import Footer from '@/app/(landing)/components/footer/footer'
import Nav from '@/app/(landing)/components/nav/nav'
import { soehne } from '@/app/fonts/soehne/soehne'
const logger = createLogger('CareersPage')

View File

@@ -7,7 +7,7 @@ import {
LinkedInIcon,
xIcon as XIcon,
} from '@/components/icons'
import { inter } from '@/app/fonts/inter/inter'
import { inter } from '@/app/_styles/fonts/inter/inter'
const blocks = [
'Agent',

View File

@@ -1,11 +1,9 @@
// Hero Components
export { IconButton } from './icon-button'
export { DotPattern } from './landing-canvas/dot-pattern'
export type {
LandingBlockProps,
LandingCardData,
} from './landing-canvas/landing-block/landing-block'
// Landing Block
export { LandingBlock } from './landing-canvas/landing-block/landing-block'
export type { LoopNodeData } from './landing-canvas/landing-block/landing-loop-node'
export { LandingLoopNode } from './landing-canvas/landing-block/landing-loop-node'
@@ -22,9 +20,7 @@ export type {
LandingManualBlock,
LandingViewportApi,
} from './landing-canvas/landing-canvas'
// Landing Canvas
export { CARD_HEIGHT, CARD_WIDTH, LandingCanvas } from './landing-canvas/landing-canvas'
// Landing Edge
export { LandingEdge } from './landing-canvas/landing-edge/landing-edge'
export type { LandingFlowProps } from './landing-canvas/landing-flow'
export { LandingFlow } from './landing-canvas/landing-flow'

View File

@@ -1,6 +1,9 @@
import React from 'react'
import { BookIcon } from 'lucide-react'
import { Tag, type TagProps } from './tag'
import {
Tag,
type TagProps,
} from '@/app/(landing)/components/hero/components/landing-canvas/landing-block/tag'
/**
* Data structure for a landing card component

View File

@@ -1,7 +1,7 @@
'use client'
import React from 'react'
import { LoopBlock } from './loop-block'
import { LoopBlock } from '@/app/(landing)/components/hero/components/landing-canvas/landing-block/loop-block'
/**
* Data structure for the loop node

View File

@@ -2,7 +2,10 @@
import React from 'react'
import { Handle, Position } from 'reactflow'
import { LandingBlock, type LandingCardData } from './landing-block'
import {
LandingBlock,
type LandingCardData,
} from '@/app/(landing)/components/hero/components/landing-canvas/landing-block/landing-block'
/**
* React Flow node component for the landing canvas

View File

@@ -3,9 +3,9 @@
import React from 'react'
import type { Edge, Node } from 'reactflow'
import { ReactFlowProvider } from 'reactflow'
import { DotPattern } from './dot-pattern'
import type { LandingCardData } from './landing-block/landing-block'
import { LandingFlow } from './landing-flow'
import { DotPattern } from '@/app/(landing)/components/hero/components/landing-canvas/dot-pattern'
import type { LandingCardData } from '@/app/(landing)/components/hero/components/landing-canvas/landing-block/landing-block'
import { LandingFlow } from '@/app/(landing)/components/hero/components/landing-canvas/landing-flow'
/**
* Visual constants for landing node dimensions

View File

@@ -3,10 +3,13 @@
import React from 'react'
import ReactFlow, { applyNodeChanges, type NodeChange, useReactFlow } from 'reactflow'
import 'reactflow/dist/style.css'
import { LandingLoopNode } from './landing-block/landing-loop-node'
import { LandingNode } from './landing-block/landing-node'
import { CARD_WIDTH, type LandingCanvasProps } from './landing-canvas'
import { LandingEdge } from './landing-edge/landing-edge'
import { LandingLoopNode } from '@/app/(landing)/components/hero/components/landing-canvas/landing-block/landing-loop-node'
import { LandingNode } from '@/app/(landing)/components/hero/components/landing-canvas/landing-block/landing-node'
import {
CARD_WIDTH,
type LandingCanvasProps,
} from '@/app/(landing)/components/hero/components/landing-canvas/landing-canvas'
import { LandingEdge } from '@/app/(landing)/components/hero/components/landing-canvas/landing-edge/landing-edge'
/**
* Props for the LandingFlow component

View File

@@ -33,7 +33,7 @@ import {
SupabaseIcon,
} from '@/components/icons'
import { LandingPromptStorage } from '@/lib/browser-storage'
import { soehne } from '@/app/fonts/soehne/soehne'
import { soehne } from '@/app/_styles/fonts/soehne/soehne'
import {
CARD_WIDTH,
IconButton,
@@ -41,7 +41,7 @@ import {
type LandingGroupData,
type LandingManualBlock,
type LandingViewportApi,
} from './components'
} from '@/app/(landing)/components/hero/components'
/**
* Service-specific template messages for the hero input

View File

@@ -1,5 +1,5 @@
import * as Icons from '@/components/icons'
import { inter } from '@/app/fonts/inter/inter'
import { inter } from '@/app/_styles/fonts/inter/inter'
const modelProviderIcons = [
{ icon: Icons.OpenAIIcon, label: 'OpenAI' },

View File

@@ -14,7 +14,7 @@ import {
import { useRouter } from 'next/navigation'
import { createLogger } from '@/lib/logs/console/logger'
import { cn } from '@/lib/utils'
import { inter } from '@/app/fonts/inter/inter'
import { inter } from '@/app/_styles/fonts/inter/inter'
import {
ENTERPRISE_PLAN_FEATURES,
PRO_PLAN_FEATURES,

View File

@@ -1,4 +1,4 @@
import { inter } from '@/app/fonts/inter/inter'
import { inter } from '@/app/_styles/fonts/inter/inter'
interface LandingTemplatePreviewProps {
previewImage: string

View File

@@ -1,7 +1,6 @@
import { inter } from '@/app/fonts/inter/inter'
import LandingTemplatePreview from './components/landing-template-preview'
import { inter } from '@/app/_styles/fonts/inter/inter'
import LandingTemplatePreview from '@/app/(landing)/components/landing-templates/components/landing-template-preview'
// Mock data for templates
const templates = [
{
id: 1,

View File

@@ -1,9 +1,9 @@
'use client'
import { isHosted } from '@/lib/environment'
import { soehne } from '@/app/_styles/fonts/soehne/soehne'
import Footer from '@/app/(landing)/components/footer/footer'
import Nav from '@/app/(landing)/components/nav/nav'
import { soehne } from '@/app/fonts/soehne/soehne'
interface LegalLayoutProps {
title: string

View File

@@ -9,8 +9,8 @@ import { GithubIcon } from '@/components/icons'
import { useBrandConfig } from '@/lib/branding/branding'
import { isHosted } from '@/lib/environment'
import { createLogger } from '@/lib/logs/console/logger'
import { soehne } from '@/app/_styles/fonts/soehne/soehne'
import { getFormattedGitHubStars } from '@/app/(landing)/actions/github'
import { soehne } from '@/app/fonts/soehne/soehne'
const logger = createLogger('nav')

View File

@@ -38,7 +38,7 @@ export default function StructuredData() {
url: 'https://sim.ai',
name: 'Sim - AI Agent Workflow Builder',
description:
'Open-source AI agent workflow builder. 50,000+ developers build and deploy agentic workflows. SOC2 and HIPAA compliant.',
'Open-source AI agent workflow builder. 60,000+ developers build and deploy agentic workflows. SOC2 and HIPAA compliant.',
publisher: {
'@id': 'https://sim.ai/#organization',
},
@@ -98,7 +98,7 @@ export default function StructuredData() {
'@id': 'https://sim.ai/#software',
name: 'Sim - AI Agent Workflow Builder',
description:
'Open-source AI agent workflow builder used by 50,000+ developers. Build agentic workflows with visual drag-and-drop interface. SOC2 and HIPAA compliant. Integrate with 100+ apps.',
'Open-source AI agent workflow builder used by 60,000+ developers. Build agentic workflows with visual drag-and-drop interface. SOC2 and HIPAA compliant. Integrate with 100+ apps.',
applicationCategory: 'DeveloperApplication',
applicationSubCategory: 'AI Development Tools',
operatingSystem: 'Web, Windows, macOS, Linux',
@@ -198,7 +198,7 @@ export default function StructuredData() {
name: 'What is Sim?',
acceptedAnswer: {
'@type': 'Answer',
text: 'Sim is an open-source AI agent workflow builder used by 50,000+ developers at trail-blazing startups to Fortune 500 companies. It provides a visual drag-and-drop interface for building and deploying agentic workflows. Sim is SOC2 and HIPAA compliant.',
text: 'Sim is an open-source AI agent workflow builder used by 60,000+ developers at trail-blazing startups to Fortune 500 companies. It provides a visual drag-and-drop interface for building and deploying agentic workflows. Sim is SOC2 and HIPAA compliant.',
},
},
{

View File

@@ -2,7 +2,7 @@
import { useEffect, useState } from 'react'
import Image from 'next/image'
import { inter } from '@/app/fonts/inter/inter'
import { inter } from '@/app/_styles/fonts/inter/inter'
interface Testimonial {
text: string

View File

@@ -5,7 +5,7 @@ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { FAQ } from '@/lib/blog/faq'
import { getAllPostMeta, getPostBySlug, getRelatedPosts } from '@/lib/blog/registry'
import { buildArticleJsonLd, buildBreadcrumbJsonLd, buildPostMetadata } from '@/lib/blog/seo'
import { soehne } from '@/app/fonts/soehne/soehne'
import { soehne } from '@/app/_styles/fonts/soehne/soehne'
export async function generateStaticParams() {
const posts = await getAllPostMeta()

View File

@@ -1,7 +1,7 @@
import Image from 'next/image'
import Link from 'next/link'
import { getAllPostMeta } from '@/lib/blog/registry'
import { soehne } from '@/app/fonts/soehne/soehne'
import { soehne } from '@/app/_styles/fonts/soehne/soehne'
export const revalidate = 3600

View File

@@ -2,7 +2,7 @@ import Image from 'next/image'
import Link from 'next/link'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { getAllPostMeta } from '@/lib/blog/registry'
import { soehne } from '@/app/fonts/soehne/soehne'
import { soehne } from '@/app/_styles/fonts/soehne/soehne'
export const revalidate = 3600

View File

@@ -3,7 +3,7 @@
import { useEffect } from 'react'
import posthog from 'posthog-js'
import { PostHogProvider as PHProvider } from 'posthog-js/react'
import { getEnv, isTruthy } from '../env'
import { getEnv, isTruthy } from '../../../lib/env'
export function PostHogProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {

View File

@@ -6,6 +6,36 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { createMockRequest } from '@/app/api/__test-utils__/utils'
vi.mock('@/lib/execution/preprocessing', () => ({
preprocessExecution: vi.fn().mockResolvedValue({
success: true,
actorUserId: 'test-user-id',
workflowRecord: {
id: 'test-workflow-id',
userId: 'test-user-id',
isDeployed: true,
workspaceId: 'test-workspace-id',
variables: {},
},
userSubscription: {
plan: 'pro',
status: 'active',
},
rateLimitInfo: {
allowed: true,
remaining: 100,
resetAt: new Date(),
},
}),
}))
vi.mock('@/lib/logs/execution/logging-session', () => ({
LoggingSession: vi.fn().mockImplementation(() => ({
safeStart: vi.fn().mockResolvedValue(undefined),
safeCompleteWithError: vi.fn().mockResolvedValue(undefined),
})),
}))
describe('Chat Identifier API Route', () => {
const createMockStream = () => {
return new ReadableStream({
@@ -307,48 +337,16 @@ describe('Chat Identifier API Route', () => {
})
it('should return 503 when workflow is not available', async () => {
// Override the default workflow result to return non-deployed
vi.doMock('@sim/db', () => {
// Track call count to return different results
let callCount = 0
const { preprocessExecution } = await import('@/lib/execution/preprocessing')
const originalImplementation = vi.mocked(preprocessExecution).getMockImplementation()
const mockLimit = vi.fn().mockImplementation(() => {
callCount++
if (callCount === 1) {
// First call - chat query
return [
{
id: 'chat-id',
workflowId: 'unavailable-workflow',
userId: 'user-id',
isActive: true,
authType: 'public',
outputConfigs: [{ blockId: 'block-1', path: 'output' }],
},
]
}
if (callCount === 2) {
// Second call - workflow query
return [
{
isDeployed: false,
},
]
}
return []
})
const mockWhere = vi.fn().mockReturnValue({ limit: mockLimit })
const mockFrom = vi.fn().mockReturnValue({ where: mockWhere })
const mockSelect = vi.fn().mockReturnValue({ from: mockFrom })
return {
db: {
select: mockSelect,
},
chat: {},
workflow: {},
}
vi.mocked(preprocessExecution).mockResolvedValueOnce({
success: false,
error: {
message: 'Workflow is not deployed',
statusCode: 403,
logCreated: true,
},
})
const req = createMockRequest('POST', { input: 'Hello' })
@@ -358,11 +356,15 @@ describe('Chat Identifier API Route', () => {
const response = await POST(req, { params })
expect(response.status).toBe(503)
expect(response.status).toBe(403)
const data = await response.json()
expect(data).toHaveProperty('error')
expect(data).toHaveProperty('message', 'Chat workflow is not available')
expect(data).toHaveProperty('message', 'Workflow is not deployed')
if (originalImplementation) {
vi.mocked(preprocessExecution).mockImplementation(originalImplementation)
}
})
it('should return streaming response for valid chat messages', async () => {
@@ -378,7 +380,6 @@ describe('Chat Identifier API Route', () => {
expect(response.headers.get('Cache-Control')).toBe('no-cache')
expect(response.headers.get('Connection')).toBe('keep-alive')
// Verify createStreamingResponse was called with correct workflow info
expect(mockCreateStreamingResponse).toHaveBeenCalledWith(
expect.objectContaining({
workflow: expect.objectContaining({
@@ -408,7 +409,6 @@ describe('Chat Identifier API Route', () => {
expect(response.status).toBe(200)
expect(response.body).toBeInstanceOf(ReadableStream)
// Test that we can read from the response stream
if (response.body) {
const reader = response.body.getReader()
const { value, done } = await reader.read()
@@ -447,7 +447,6 @@ describe('Chat Identifier API Route', () => {
})
it('should handle invalid JSON in request body', async () => {
// Create a request with invalid JSON
const req = {
method: 'POST',
headers: new Headers(),

View File

@@ -1,9 +1,10 @@
import { randomUUID } from 'crypto'
import { db } from '@sim/db'
import { chat, workflow, workspace } from '@sim/db/schema'
import { chat } from '@sim/db/schema'
import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { v4 as uuidv4 } from 'uuid'
import { z } from 'zod'
import { preprocessExecution } from '@/lib/execution/preprocessing'
import { createLogger } from '@/lib/logs/console/logger'
import { LoggingSession } from '@/lib/logs/execution/logging-session'
import { ChatFiles } from '@/lib/uploads'
@@ -93,7 +94,7 @@ export async function POST(
if (!deployment.isActive) {
logger.warn(`[${requestId}] Chat is not active: ${identifier}`)
const executionId = uuidv4()
const executionId = randomUUID()
const loggingSession = new LoggingSession(
deployment.workflowId,
executionId,
@@ -140,82 +141,35 @@ export async function POST(
return addCorsHeaders(createErrorResponse('No input provided', 400), request)
}
const workflowResult = await db
.select({
isDeployed: workflow.isDeployed,
workspaceId: workflow.workspaceId,
variables: workflow.variables,
})
.from(workflow)
.where(eq(workflow.id, deployment.workflowId))
.limit(1)
const executionId = randomUUID()
if (workflowResult.length === 0 || !workflowResult[0].isDeployed) {
logger.warn(`[${requestId}] Workflow not found or not deployed: ${deployment.workflowId}`)
const loggingSession = new LoggingSession(deployment.workflowId, executionId, 'chat', requestId)
const executionId = uuidv4()
const loggingSession = new LoggingSession(
deployment.workflowId,
executionId,
'chat',
requestId
const preprocessResult = await preprocessExecution({
workflowId: deployment.workflowId,
userId: deployment.userId,
triggerType: 'chat',
executionId,
requestId,
checkRateLimit: false, // Chat bypasses rate limits
checkDeployment: true, // Chat requires deployed workflows
loggingSession,
})
if (!preprocessResult.success) {
logger.warn(`[${requestId}] Preprocessing failed: ${preprocessResult.error?.message}`)
return addCorsHeaders(
createErrorResponse(
preprocessResult.error?.message || 'Failed to process request',
preprocessResult.error?.statusCode || 500
),
request
)
await loggingSession.safeStart({
userId: deployment.userId,
workspaceId: workflowResult[0]?.workspaceId || '',
variables: {},
})
await loggingSession.safeCompleteWithError({
error: {
message: 'Chat workflow is not available. The workflow is not deployed.',
stackTrace: undefined,
},
traceSpans: [],
})
return addCorsHeaders(createErrorResponse('Chat workflow is not available', 503), request)
}
let workspaceOwnerId = deployment.userId
if (workflowResult[0].workspaceId) {
const workspaceData = await db
.select({ ownerId: workspace.ownerId })
.from(workspace)
.where(eq(workspace.id, workflowResult[0].workspaceId))
.limit(1)
if (workspaceData.length === 0) {
logger.error(`[${requestId}] Workspace not found for workflow ${deployment.workflowId}`)
const executionId = uuidv4()
const loggingSession = new LoggingSession(
deployment.workflowId,
executionId,
'chat',
requestId
)
await loggingSession.safeStart({
userId: deployment.userId,
workspaceId: workflowResult[0].workspaceId || '',
variables: {},
})
await loggingSession.safeCompleteWithError({
error: {
message: 'Workspace not found. Critical configuration error - please contact support.',
stackTrace: undefined,
},
traceSpans: [],
})
return addCorsHeaders(createErrorResponse('Workspace not found', 500), request)
}
workspaceOwnerId = workspaceData[0].ownerId
}
const { actorUserId, workflowRecord } = preprocessResult
const workspaceOwnerId = actorUserId!
const workspaceId = workflowRecord?.workspaceId || ''
try {
const selectedOutputs: string[] = []
@@ -232,12 +186,10 @@ export async function POST(
const { SSE_HEADERS } = await import('@/lib/utils')
const { createFilteredResult } = await import('@/app/api/workflows/[id]/execute/route')
const executionId = crypto.randomUUID()
const workflowInput: any = { input, conversationId }
if (files && Array.isArray(files) && files.length > 0) {
const executionContext = {
workspaceId: workflowResult[0].workspaceId || '',
workspaceId,
workflowId: deployment.workflowId,
executionId,
}
@@ -257,20 +209,13 @@ export async function POST(
} catch (fileError: any) {
logger.error(`[${requestId}] Failed to process chat files:`, fileError)
const fileLoggingSession = new LoggingSession(
deployment.workflowId,
executionId,
'chat',
requestId
)
await fileLoggingSession.safeStart({
await loggingSession.safeStart({
userId: workspaceOwnerId,
workspaceId: workflowResult[0].workspaceId || '',
workspaceId,
variables: {},
})
await fileLoggingSession.safeCompleteWithError({
await loggingSession.safeCompleteWithError({
error: {
message: `File upload failed: ${fileError.message || 'Unable to process uploaded files'}`,
stackTrace: fileError.stack,
@@ -285,9 +230,9 @@ export async function POST(
const workflowForExecution = {
id: deployment.workflowId,
userId: deployment.userId,
workspaceId: workflowResult[0].workspaceId,
isDeployed: true,
variables: workflowResult[0].variables || {},
workspaceId,
isDeployed: workflowRecord?.isDeployed ?? false,
variables: workflowRecord?.variables || {},
}
const stream = await createStreamingResponse({

View File

@@ -19,6 +19,7 @@ describe('Chat Edit API Route', () => {
const mockCreateErrorResponse = vi.fn()
const mockEncryptSecret = vi.fn()
const mockCheckChatAccess = vi.fn()
const mockGetSession = vi.fn()
beforeEach(() => {
vi.resetModules()
@@ -42,6 +43,10 @@ describe('Chat Edit API Route', () => {
chat: { id: 'id', identifier: 'identifier', userId: 'userId' },
}))
vi.doMock('@/lib/auth', () => ({
getSession: mockGetSession,
}))
vi.doMock('@/lib/logs/console/logger', () => ({
createLogger: vi.fn().mockReturnValue({
info: vi.fn(),
@@ -89,9 +94,7 @@ describe('Chat Edit API Route', () => {
describe('GET', () => {
it('should return 401 when user is not authenticated', async () => {
vi.doMock('@/lib/auth', () => ({
getSession: vi.fn().mockResolvedValue(null),
}))
mockGetSession.mockResolvedValueOnce(null)
const req = new NextRequest('http://localhost:3000/api/chat/manage/chat-123')
const { GET } = await import('@/app/api/chat/manage/[id]/route')
@@ -102,11 +105,9 @@ describe('Chat Edit API Route', () => {
})
it('should return 404 when chat not found or access denied', async () => {
vi.doMock('@/lib/auth', () => ({
getSession: vi.fn().mockResolvedValue({
user: { id: 'user-id' },
}),
}))
mockGetSession.mockResolvedValueOnce({
user: { id: 'user-id' },
})
mockCheckChatAccess.mockResolvedValue({ hasAccess: false })

View File

@@ -71,13 +71,13 @@ describe('Chat API Utils', () => {
})
describe('Auth token utils', () => {
it('should encrypt and validate auth tokens', async () => {
const { encryptAuthToken, validateAuthToken } = await import('@/app/api/chat/utils')
it('should validate auth tokens', async () => {
const { validateAuthToken } = await import('@/app/api/chat/utils')
const chatId = 'test-chat-id'
const type = 'password'
const token = encryptAuthToken(chatId, type)
const token = Buffer.from(`${chatId}:${type}:${Date.now()}`).toString('base64')
expect(typeof token).toBe('string')
expect(token.length).toBeGreaterThan(0)
@@ -92,7 +92,6 @@ describe('Chat API Utils', () => {
const { validateAuthToken } = await import('@/app/api/chat/utils')
const chatId = 'test-chat-id'
// Create an expired token by directly constructing it with an old timestamp
const expiredToken = Buffer.from(
`${chatId}:password:${Date.now() - 25 * 60 * 60 * 1000}`
).toString('base64')
@@ -166,20 +165,6 @@ describe('Chat API Utils', () => {
'Content-Type, X-Requested-With'
)
})
it('should handle OPTIONS request', async () => {
const { OPTIONS } = await import('@/app/api/chat/utils')
const mockRequest = {
headers: {
get: vi.fn().mockReturnValue('http://localhost:3000'),
},
} as any
const response = await OPTIONS(mockRequest)
expect(response.status).toBe(204)
})
})
describe('Chat auth validation', () => {
@@ -355,10 +340,8 @@ describe('Chat API Utils', () => {
describe('Execution Result Processing', () => {
it('should process logs regardless of overall success status', () => {
// Test that logs are processed even when overall execution fails
// This is key for partial success scenarios
const executionResult = {
success: false, // Overall execution failed
success: false,
output: {},
logs: [
{
@@ -383,16 +366,13 @@ describe('Chat API Utils', () => {
metadata: { duration: 1000 },
}
// Test the key logic: logs should be processed regardless of overall success
expect(executionResult.success).toBe(false)
expect(executionResult.logs).toBeDefined()
expect(executionResult.logs).toHaveLength(2)
// First log should be successful
expect(executionResult.logs[0].success).toBe(true)
expect(executionResult.logs[0].output?.content).toBe('Agent 1 succeeded')
// Second log should be failed
expect(executionResult.logs[1].success).toBe(false)
expect(executionResult.logs[1].error).toBe('Agent 2 failed')
})
@@ -405,18 +385,15 @@ describe('Chat API Utils', () => {
metadata: { duration: 100 },
}
// Test direct ExecutionResult
const directResult = executionResult
const extractedDirect = directResult
expect(extractedDirect).toBe(executionResult)
// Test StreamingExecution with embedded ExecutionResult
const streamingResult = {
stream: new ReadableStream(),
execution: executionResult,
}
// Test that streaming execution wraps the result correctly
const extractedFromStreaming =
streamingResult && typeof streamingResult === 'object' && 'execution' in streamingResult
? streamingResult.execution

View File

@@ -1,7 +1,7 @@
import { db } from '@sim/db'
import { chat, workflow } from '@sim/db/schema'
import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import type { NextRequest, NextResponse } from 'next/server'
import { isDev } from '@/lib/environment'
import { createLogger } from '@/lib/logs/console/logger'
import { hasAdminPermission } from '@/lib/permissions/utils'
@@ -77,7 +77,7 @@ export async function checkChatAccess(
return { hasAccess: false }
}
export const encryptAuthToken = (chatId: string, type: string): string => {
const encryptAuthToken = (chatId: string, type: string): string => {
return Buffer.from(`${chatId}:${type}:${Date.now()}`).toString('base64')
}
@@ -104,7 +104,6 @@ export const validateAuthToken = (token: string, chatId: string): boolean => {
}
}
// Set cookie helper function
export const setChatAuthCookie = (response: NextResponse, chatId: string, type: string): void => {
const token = encryptAuthToken(chatId, type)
response.cookies.set({
@@ -118,7 +117,6 @@ export const setChatAuthCookie = (response: NextResponse, chatId: string, type:
})
}
// Helper function to add CORS headers to responses
export function addCorsHeaders(response: NextResponse, request: NextRequest) {
const origin = request.headers.get('origin') || ''
@@ -132,12 +130,6 @@ export function addCorsHeaders(response: NextResponse, request: NextRequest) {
return response
}
export async function OPTIONS(request: NextRequest) {
const response = new NextResponse(null, { status: 204 })
return addCorsHeaders(response, request)
}
// Validate authentication for chat access
export async function validateChatAuth(
requestId: string,
deployment: any,
@@ -146,12 +138,10 @@ export async function validateChatAuth(
): Promise<{ authorized: boolean; error?: string }> {
const authType = deployment.authType || 'public'
// Public chats are accessible to everyone
if (authType === 'public') {
return { authorized: true }
}
// Check for auth cookie first
const cookieName = `chat_auth_${deployment.id}`
const authCookie = request.cookies.get(cookieName)
@@ -159,9 +149,7 @@ export async function validateChatAuth(
return { authorized: true }
}
// For password protection, check the password in the request body
if (authType === 'password') {
// For GET requests, we just notify the client that authentication is required
if (request.method === 'GET') {
return { authorized: false, error: 'auth_required_password' }
}
@@ -198,22 +186,18 @@ export async function validateChatAuth(
}
}
// For email access control, check the email in the request body
if (authType === 'email') {
// For GET requests, we just notify the client that authentication is required
if (request.method === 'GET') {
return { authorized: false, error: 'auth_required_email' }
}
try {
// Use the parsed body if provided, otherwise the auth check is not applicable
if (!parsedBody) {
return { authorized: false, error: 'Email is required' }
}
const { email, input } = parsedBody
// If this is a chat message, not an auth attempt
if (input && !email) {
return { authorized: false, error: 'auth_required_email' }
}
@@ -224,17 +208,12 @@ export async function validateChatAuth(
const allowedEmails = deployment.allowedEmails || []
// Check exact email matches
if (allowedEmails.includes(email)) {
// Email is allowed but still needs OTP verification
// Return a special error code that the client will recognize
return { authorized: false, error: 'otp_required' }
}
// Check domain matches (prefixed with @)
const domain = email.split('@')[1]
if (domain && allowedEmails.some((allowed: string) => allowed === `@${domain}`)) {
// Domain is allowed but still needs OTP verification
return { authorized: false, error: 'otp_required' }
}
@@ -257,6 +236,10 @@ export async function validateChatAuth(
const { email, input, checkSSOAccess } = parsedBody
if (input && !checkSSOAccess) {
return { authorized: false, error: 'auth_required_sso' }
}
if (checkSSOAccess) {
if (!email) {
return { authorized: false, error: 'Email is required' }

View File

@@ -563,6 +563,8 @@ describe('Copilot Chat API Route', () => {
],
messageCount: 4,
previewYaml: null,
config: null,
planArtifact: null,
createdAt: '2024-01-01T00:00:00.000Z',
updatedAt: '2024-01-02T00:00:00.000Z',
},
@@ -576,6 +578,8 @@ describe('Copilot Chat API Route', () => {
],
messageCount: 2,
previewYaml: null,
config: null,
planArtifact: null,
createdAt: '2024-01-03T00:00:00.000Z',
updatedAt: '2024-01-04T00:00:00.000Z',
},

View File

@@ -43,6 +43,12 @@ const ChatMessageSchema = z.object({
'gpt-5',
'gpt-5-medium',
'gpt-5-high',
'gpt-5.1-fast',
'gpt-5.1',
'gpt-5.1-medium',
'gpt-5.1-high',
'gpt-5-codex',
'gpt-5.1-codex',
'gpt-4o',
'gpt-4.1',
'o3',
@@ -53,7 +59,7 @@ const ChatMessageSchema = z.object({
])
.optional()
.default('claude-4.5-sonnet'),
mode: z.enum(['ask', 'agent']).optional().default('agent'),
mode: z.enum(['ask', 'agent', 'plan']).optional().default('agent'),
prefetch: z.boolean().optional(),
createNewChat: z.boolean().optional().default(false),
stream: z.boolean().optional().default(true),
@@ -880,6 +886,8 @@ export async function GET(req: NextRequest) {
title: copilotChats.title,
model: copilotChats.model,
messages: copilotChats.messages,
planArtifact: copilotChats.planArtifact,
config: copilotChats.config,
createdAt: copilotChats.createdAt,
updatedAt: copilotChats.updatedAt,
})
@@ -897,6 +905,8 @@ export async function GET(req: NextRequest) {
messages: Array.isArray(chat.messages) ? chat.messages : [],
messageCount: Array.isArray(chat.messages) ? chat.messages.length : 0,
previewYaml: null, // Not needed for chat list
planArtifact: chat.planArtifact || null,
config: chat.config || null,
createdAt: chat.createdAt,
updatedAt: chat.updatedAt,
}))

View File

@@ -37,6 +37,14 @@ const UpdateMessagesSchema = z.object({
.optional(),
})
),
planArtifact: z.string().nullable().optional(),
config: z
.object({
mode: z.enum(['ask', 'build', 'plan']).optional(),
model: z.string().optional(),
})
.nullable()
.optional(),
})
export async function POST(req: NextRequest) {
@@ -49,7 +57,7 @@ export async function POST(req: NextRequest) {
}
const body = await req.json()
const { chatId, messages } = UpdateMessagesSchema.parse(body)
const { chatId, messages, planArtifact, config } = UpdateMessagesSchema.parse(body)
// Verify that the chat belongs to the user
const [chat] = await db
@@ -62,18 +70,27 @@ export async function POST(req: NextRequest) {
return createNotFoundResponse('Chat not found or unauthorized')
}
// Update chat with new messages
await db
.update(copilotChats)
.set({
messages: messages,
updatedAt: new Date(),
})
.where(eq(copilotChats.id, chatId))
// Update chat with new messages, plan artifact, and config
const updateData: Record<string, any> = {
messages: messages,
updatedAt: new Date(),
}
logger.info(`[${tracker.requestId}] Successfully updated chat messages`, {
if (planArtifact !== undefined) {
updateData.planArtifact = planArtifact
}
if (config !== undefined) {
updateData.config = config
}
await db.update(copilotChats).set(updateData).where(eq(copilotChats.id, chatId))
logger.info(`[${tracker.requestId}] Successfully updated chat`, {
chatId,
newMessageCount: messages.length,
hasPlanArtifact: !!planArtifact,
hasConfig: !!config,
})
return NextResponse.json({

View File

@@ -12,8 +12,14 @@ const DEFAULT_ENABLED_MODELS: Record<string, boolean> = {
'gpt-4.1': false,
'gpt-5-fast': false,
'gpt-5': true,
'gpt-5-medium': true,
'gpt-5-medium': false,
'gpt-5-high': false,
'gpt-5.1-fast': false,
'gpt-5.1': true,
'gpt-5.1-medium': true,
'gpt-5.1-high': false,
'gpt-5-codex': false,
'gpt-5.1-codex': true,
o3: true,
'claude-4-sonnet': false,
'claude-4.5-haiku': true,

View File

@@ -7,7 +7,7 @@ import { z } from 'zod'
import { getSession } from '@/lib/auth'
import { createLogger } from '@/lib/logs/console/logger'
import { generateRequestId } from '@/lib/utils'
import type { CreatorProfileDetails } from '@/types/creator-profile'
import type { CreatorProfileDetails } from '@/app/_types/creator-profile'
const logger = createLogger('CreatorProfilesAPI')

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { createFileResponse, extractFilename, findLocalFile } from './utils'
import { createFileResponse, extractFilename, findLocalFile } from '@/app/api/files/utils'
describe('extractFilename', () => {
describe('legitimate file paths', () => {

View File

@@ -6,8 +6,6 @@ import { createLogger } from '@/lib/logs/console/logger'
import { estimateTokenCount } from '@/lib/tokenization/estimators'
import { generateRequestId } from '@/lib/utils'
import { getUserId } from '@/app/api/auth/oauth/utils'
import { checkKnowledgeBaseAccess } from '@/app/api/knowledge/utils'
import { calculateCost } from '@/providers/utils'
import {
generateSearchEmbedding,
getDocumentNamesByIds,
@@ -16,7 +14,9 @@ import {
handleTagOnlySearch,
handleVectorOnlySearch,
type SearchResult,
} from './utils'
} from '@/app/api/knowledge/search/utils'
import { checkKnowledgeBaseAccess } from '@/app/api/knowledge/utils'
import { calculateCost } from '@/providers/utils'
const logger = createLogger('VectorSearchAPI')

View File

@@ -42,7 +42,7 @@ import {
handleTagAndVectorSearch,
handleTagOnlySearch,
handleVectorOnlySearch,
} from './utils'
} from '@/app/api/knowledge/search/utils'
describe('Knowledge Search Utils', () => {
beforeEach(() => {

View File

@@ -1,5 +1,8 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import { preprocessExecution } from '@/lib/execution/preprocessing'
import { createLogger } from '@/lib/logs/console/logger'
import { generateRequestId } from '@/lib/utils'
import { PauseResumeManager } from '@/lib/workflows/executor/human-in-the-loop-manager'
import { validateWorkflowAccess } from '@/app/api/workflows/middleware'
@@ -35,6 +38,54 @@ export async function POST(
const resumeInput = payload?.input ?? payload ?? {}
const userId = workflow.userId ?? ''
const resumeExecutionId = randomUUID()
const requestId = generateRequestId()
logger.info(`[${requestId}] Preprocessing resume execution`, {
workflowId,
parentExecutionId: executionId,
resumeExecutionId,
userId,
})
const preprocessResult = await preprocessExecution({
workflowId,
userId,
triggerType: 'manual', // Resume is a manual trigger
executionId: resumeExecutionId,
requestId,
checkRateLimit: false, // Manual triggers bypass rate limits
checkDeployment: false, // Resuming existing execution, deployment already checked
skipUsageLimits: true, // Resume is continuation of authorized execution - don't recheck limits
workspaceId: workflow.workspaceId || undefined,
isResumeContext: true, // Enable billing fallback for paused workflow resumes
})
if (!preprocessResult.success) {
logger.warn(`[${requestId}] Preprocessing failed for resume`, {
workflowId,
parentExecutionId: executionId,
error: preprocessResult.error?.message,
statusCode: preprocessResult.error?.statusCode,
})
return NextResponse.json(
{
error:
preprocessResult.error?.message ||
'Failed to validate resume execution. Please try again.',
},
{ status: preprocessResult.error?.statusCode || 400 }
)
}
logger.info(`[${requestId}] Preprocessing passed, proceeding with resume`, {
workflowId,
parentExecutionId: executionId,
resumeExecutionId,
actorUserId: preprocessResult.actorUserId,
})
try {
const enqueueResult = await PauseResumeManager.enqueueOrStartResume({
executionId,

View File

@@ -0,0 +1,140 @@
import { db } from '@sim/db'
import { templates } from '@sim/db/schema'
import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { checkInternalApiKey } from '@/lib/copilot/utils'
import { createLogger } from '@/lib/logs/console/logger'
import { generateRequestId } from '@/lib/utils'
import { sanitizeForCopilot } from '@/lib/workflows/json-sanitizer'
const logger = createLogger('TemplatesSanitizedAPI')
export const revalidate = 0
/**
* GET /api/templates/approved/sanitized
* Returns all approved templates with their sanitized JSONs, names, and descriptions
* Requires internal API secret authentication via X-API-Key header
*/
export async function GET(request: NextRequest) {
const requestId = generateRequestId()
try {
const url = new URL(request.url)
const hasApiKey = !!request.headers.get('x-api-key')
// Check internal API key authentication
const authResult = checkInternalApiKey(request)
if (!authResult.success) {
logger.warn(`[${requestId}] Authentication failed for approved sanitized templates`, {
error: authResult.error,
hasApiKey,
howToUse: 'Add header: X-API-Key: <INTERNAL_API_SECRET>',
})
return NextResponse.json(
{
error: authResult.error,
hint: 'Include X-API-Key header with INTERNAL_API_SECRET value',
},
{ status: 401 }
)
}
// Fetch all approved templates
const approvedTemplates = await db
.select({
id: templates.id,
name: templates.name,
details: templates.details,
state: templates.state,
tags: templates.tags,
requiredCredentials: templates.requiredCredentials,
})
.from(templates)
.where(eq(templates.status, 'approved'))
// Process each template to sanitize for copilot
const sanitizedTemplates = approvedTemplates
.map((template) => {
try {
const copilotSanitized = sanitizeForCopilot(template.state as any)
if (copilotSanitized?.blocks) {
Object.values(copilotSanitized.blocks).forEach((block: any) => {
if (block && typeof block === 'object') {
block.outputs = undefined
block.position = undefined
block.height = undefined
block.layout = undefined
block.horizontalHandles = undefined
// Also clean nested nodes recursively
if (block.nestedNodes) {
Object.values(block.nestedNodes).forEach((nestedBlock: any) => {
if (nestedBlock && typeof nestedBlock === 'object') {
nestedBlock.outputs = undefined
nestedBlock.position = undefined
nestedBlock.height = undefined
nestedBlock.layout = undefined
nestedBlock.horizontalHandles = undefined
}
})
}
}
})
}
const details = template.details as { tagline?: string; about?: string } | null
const description = details?.tagline || details?.about || ''
return {
id: template.id,
name: template.name,
description,
tags: template.tags,
requiredCredentials: template.requiredCredentials,
sanitizedJson: copilotSanitized,
}
} catch (error) {
logger.error(`[${requestId}] Error sanitizing template ${template.id}`, {
error: error instanceof Error ? error.message : String(error),
})
return null
}
})
.filter((t): t is NonNullable<typeof t> => t !== null)
const response = {
templates: sanitizedTemplates,
count: sanitizedTemplates.length,
}
return NextResponse.json(response)
} catch (error) {
logger.error(`[${requestId}] Error fetching approved sanitized templates`, {
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
})
return NextResponse.json(
{
error: 'Internal server error',
requestId,
},
{ status: 500 }
)
}
}
// Add a helpful OPTIONS handler for CORS preflight
export async function OPTIONS(request: NextRequest) {
const requestId = generateRequestId()
logger.info(`[${requestId}] OPTIONS request received for /api/templates/approved/sanitized`)
return new NextResponse(null, {
status: 200,
headers: {
'Access-Control-Allow-Methods': 'GET, OPTIONS',
'Access-Control-Allow-Headers': 'X-API-Key, Content-Type',
},
})
}

View File

@@ -1,8 +1,8 @@
import { type NextRequest, NextResponse } from 'next/server'
import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription'
import { createLogger } from '@/lib/logs/console/logger'
import { authenticateV1Request } from '@/app/api/v1/auth'
import { RateLimiter } from '@/services/queue/RateLimiter'
import { authenticateV1Request } from './auth'
const logger = createLogger('V1Middleware')
const rateLimiter = new RateLimiter()

View File

@@ -2,7 +2,7 @@ import { type NextRequest, NextResponse } from 'next/server'
import { createLogger } from '@/lib/logs/console/logger'
import { generateRequestId } from '@/lib/utils'
import {
checkRateLimits,
checkWebhookPreprocessing,
findWebhookAndWorkflow,
handleProviderChallenges,
parseWebhookBody,
@@ -67,9 +67,39 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
return authError
}
const rateLimitError = await checkRateLimits(foundWorkflow, foundWebhook, requestId)
if (rateLimitError) {
return rateLimitError
let preprocessError: NextResponse | null = null
try {
preprocessError = await checkWebhookPreprocessing(
foundWorkflow,
foundWebhook,
requestId,
true // testMode - skips usage limits
)
if (preprocessError) {
return preprocessError
}
} catch (error) {
logger.error(`[${requestId}] Unexpected error during webhook preprocessing`, {
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
webhookId: foundWebhook.id,
workflowId: foundWorkflow.id,
})
if (foundWebhook.provider === 'microsoft-teams') {
return NextResponse.json(
{
type: 'message',
text: 'An unexpected error occurred during preprocessing',
},
{ status: 500 }
)
}
return NextResponse.json(
{ error: 'An unexpected error occurred during preprocessing' },
{ status: 500 }
)
}
logger.info(

View File

@@ -88,6 +88,35 @@ vi.mock('@/executor', () => ({
})),
}))
vi.mock('@/lib/execution/preprocessing', () => ({
preprocessExecution: vi.fn().mockResolvedValue({
success: true,
actorUserId: 'test-user-id',
workflowRecord: {
id: 'test-workflow-id',
userId: 'test-user-id',
isDeployed: true,
workspaceId: 'test-workspace-id',
},
userSubscription: {
plan: 'pro',
status: 'active',
},
rateLimitInfo: {
allowed: true,
remaining: 100,
resetAt: new Date(),
},
}),
}))
vi.mock('@/lib/logs/execution/logging-session', () => ({
LoggingSession: vi.fn().mockImplementation(() => ({
safeStart: vi.fn().mockResolvedValue(undefined),
safeCompleteWithError: vi.fn().mockResolvedValue(undefined),
})),
}))
process.env.DATABASE_URL = 'postgresql://test:test@localhost:5432/test'
vi.mock('drizzle-orm/postgres-js', () => ({
@@ -190,19 +219,6 @@ describe('Webhook Trigger API Route', () => {
})
describe('Generic Webhook Authentication', () => {
beforeEach(() => {
vi.doMock('@/lib/billing/core/subscription', () => ({
getHighestPrioritySubscription: vi.fn().mockResolvedValue({
plan: 'pro',
status: 'active',
}),
}))
vi.doMock('@/lib/billing', () => ({
checkServerSideUsageLimits: vi.fn().mockResolvedValue(null),
}))
})
it('should process generic webhook without authentication', async () => {
globalMockData.webhooks.push({
id: 'generic-webhook-id',

View File

@@ -2,8 +2,7 @@ import { type NextRequest, NextResponse } from 'next/server'
import { createLogger } from '@/lib/logs/console/logger'
import { generateRequestId } from '@/lib/utils'
import {
checkRateLimits,
checkUsageLimits,
checkWebhookPreprocessing,
findWebhookAndWorkflow,
handleProviderChallenges,
parseWebhookBody,
@@ -124,14 +123,39 @@ export async function POST(
return authError
}
const rateLimitError = await checkRateLimits(foundWorkflow, foundWebhook, requestId)
if (rateLimitError) {
return rateLimitError
}
let preprocessError: NextResponse | null = null
try {
preprocessError = await checkWebhookPreprocessing(
foundWorkflow,
foundWebhook,
requestId,
false // testMode
)
if (preprocessError) {
return preprocessError
}
} catch (error) {
logger.error(`[${requestId}] Unexpected error during webhook preprocessing`, {
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
webhookId: foundWebhook.id,
workflowId: foundWorkflow.id,
})
const usageLimitError = await checkUsageLimits(foundWorkflow, foundWebhook, requestId, false)
if (usageLimitError) {
return usageLimitError
if (foundWebhook.provider === 'microsoft-teams') {
return NextResponse.json(
{
type: 'message',
text: 'An unexpected error occurred during preprocessing',
},
{ status: 500 }
)
}
return NextResponse.json(
{ error: 'An unexpected error occurred during preprocessing' },
{ status: 500 }
)
}
if (foundWebhook.blockId) {

View File

@@ -2,8 +2,8 @@ import { type NextRequest, NextResponse } from 'next/server'
import { validate as uuidValidate, v4 as uuidv4 } from 'uuid'
import { z } from 'zod'
import { checkHybridAuth } from '@/lib/auth/hybrid'
import { checkServerSideUsageLimits } from '@/lib/billing'
import { processInputFileFields } from '@/lib/execution/files'
import { preprocessExecution } from '@/lib/execution/preprocessing'
import { createLogger } from '@/lib/logs/console/logger'
import { LoggingSession } from '@/lib/logs/execution/logging-session'
import { generateRequestId, SSE_HEADERS } from '@/lib/utils'
@@ -16,7 +16,6 @@ import { type ExecutionEvent, encodeSSEEvent } from '@/lib/workflows/executor/ex
import { PauseResumeManager } from '@/lib/workflows/executor/human-in-the-loop-manager'
import { createStreamingResponse } from '@/lib/workflows/streaming'
import { createHttpResponseFromBlock, workflowHasResponseBlock } from '@/lib/workflows/utils'
import { validateWorkflowAccess } from '@/app/api/workflows/middleware'
import { type ExecutionMetadata, ExecutionSnapshot } from '@/executor/execution/snapshot'
import type { StreamingExecution } from '@/executor/types'
import { Serializer } from '@/serializer'
@@ -30,7 +29,6 @@ const ExecuteWorkflowSchema = z.object({
stream: z.boolean().optional(),
useDraftState: z.boolean().optional(),
input: z.any().optional(),
startBlockId: z.string().optional(),
// Optional workflow state override (for executing diff workflows)
workflowStateOverride: z
.object({
@@ -45,30 +43,21 @@ const ExecuteWorkflowSchema = z.object({
export const runtime = 'nodejs'
export const dynamic = 'force-dynamic'
class UsageLimitError extends Error {
statusCode: number
constructor(message: string, statusCode = 402) {
super(message)
this.statusCode = statusCode
}
}
/**
* Execute workflow with streaming support - used by chat and other streaming endpoints
*
* This function assumes preprocessing has already been completed.
* Callers must run preprocessExecution() first to validate workflow, check usage limits,
* and resolve actor before calling this function.
*
* This is a wrapper function that:
* - Checks usage limits before execution (protects chat and streaming paths)
* - Logs usage limit errors to the database for user visibility
* - Supports streaming callbacks (onStream, onBlockComplete)
* - Returns ExecutionResult instead of NextResponse
* - Handles pause/resume logic
*
* Used by:
* - Chat execution (/api/chat/[identifier]/route.ts)
* - Streaming responses (lib/workflows/streaming.ts)
*
* Note: The POST handler in this file calls executeWorkflowCore() directly and has
* its own usage check. This wrapper provides convenience and built-in protection
* for callers that need streaming support.
*/
export async function executeWorkflow(
workflow: any,
@@ -92,38 +81,6 @@ export async function executeWorkflow(
const loggingSession = new LoggingSession(workflowId, executionId, triggerType, requestId)
try {
const usageCheck = await checkServerSideUsageLimits(actorUserId)
if (usageCheck.isExceeded) {
logger.warn(
`[${requestId}] User ${actorUserId} has exceeded usage limits. Blocking workflow execution.`,
{
currentUsage: usageCheck.currentUsage,
limit: usageCheck.limit,
workflowId,
triggerType,
}
)
await loggingSession.safeStart({
userId: actorUserId,
workspaceId: workflow.workspaceId || '',
variables: {},
})
await loggingSession.safeCompleteWithError({
error: {
message:
usageCheck.message || 'Usage limit exceeded. Please upgrade your plan to continue.',
stackTrace: undefined,
},
traceSpans: [],
})
throw new UsageLimitError(
usageCheck.message || 'Usage limit exceeded. Please upgrade your plan to continue.'
)
}
const metadata: ExecutionMetadata = {
requestId,
executionId,
@@ -270,23 +227,12 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
const { id: workflowId } = await params
try {
// Authenticate user (API key, session, or internal JWT)
const auth = await checkHybridAuth(req, { requireWorkflowId: false })
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const userId = auth.userId
// Validate workflow access (don't require deployment for manual client runs)
const workflowValidation = await validateWorkflowAccess(req, workflowId, false)
if (workflowValidation.error) {
return NextResponse.json(
{ error: workflowValidation.error.message },
{ status: workflowValidation.error.status }
)
}
const workflow = workflowValidation.workflow!
let body: any = {}
try {
const text = await req.text()
@@ -375,43 +321,33 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
requestId
)
// Check usage limits for this POST handler execution path
// Architecture note: This handler calls executeWorkflowCore() directly (both SSE and non-SSE paths).
// The executeWorkflow() wrapper function (used by chat) has its own check (line 54).
const usageCheck = await checkServerSideUsageLimits(userId)
if (usageCheck.isExceeded) {
logger.warn(`[${requestId}] User ${userId} has exceeded usage limits. Blocking execution.`, {
currentUsage: usageCheck.currentUsage,
limit: usageCheck.limit,
workflowId,
triggerType,
})
await loggingSession.safeStart({
userId,
workspaceId: workflow.workspaceId || '',
variables: {},
})
await loggingSession.safeCompleteWithError({
error: {
message:
usageCheck.message || 'Usage limit exceeded. Please upgrade your plan to continue.',
stackTrace: undefined,
},
traceSpans: [],
})
const preprocessResult = await preprocessExecution({
workflowId,
userId,
triggerType: loggingTriggerType,
executionId,
requestId,
checkRateLimit: false, // Manual executions bypass rate limits
checkDeployment: !shouldUseDraftState, // Check deployment unless using draft
loggingSession,
})
if (!preprocessResult.success) {
return NextResponse.json(
{
error:
usageCheck.message || 'Usage limit exceeded. Please upgrade your plan to continue.',
},
{ status: 402 }
{ error: preprocessResult.error!.message },
{ status: preprocessResult.error!.statusCode }
)
}
// Process file fields in workflow input (base64/URL to UserFile conversion)
const actorUserId = preprocessResult.actorUserId!
const workflow = preprocessResult.workflowRecord!
logger.info(`[${requestId}] Preprocessing passed`, {
workflowId,
actorUserId,
workspaceId: workflow.workspaceId,
})
let processedInput = input
try {
const workflowData = shouldUseDraftState
@@ -438,14 +374,14 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
serializedWorkflow.blocks,
executionContext,
requestId,
userId
actorUserId
)
}
} catch (fileError) {
logger.error(`[${requestId}] Failed to process input file fields:`, fileError)
await loggingSession.safeStart({
userId,
userId: actorUserId,
workspaceId: workflow.workspaceId || '',
variables: {},
})
@@ -473,8 +409,8 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
requestId,
executionId,
workflowId,
workspaceId: workflow.workspaceId,
userId,
workspaceId: workflow.workspaceId ?? undefined,
userId: actorUserId,
triggerType,
useDraftState: shouldUseDraftState,
startTime: new Date().toISOString(),
@@ -516,8 +452,6 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
return NextResponse.json(filteredResult)
} catch (error: any) {
// Block errors are already logged with full details by BlockExecutor
// Only log the error message here to avoid duplicate logging
const errorMessage = error.message || 'Unknown error'
logger.error(`[${requestId}] Non-SSE execution failed: ${errorMessage}`)
@@ -549,9 +483,15 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
const resolvedSelectedOutputs = resolveOutputIds(selectedOutputs, deployedData?.blocks || {})
const stream = await createStreamingResponse({
requestId,
workflow,
workflow: {
id: workflow.id,
userId: actorUserId,
workspaceId: workflow.workspaceId,
isDeployed: workflow.isDeployed,
variables: (workflow as any).variables,
},
input: processedInput,
executingUserId: userId,
executingUserId: actorUserId,
streamConfig: {
selectedOutputs: resolvedSelectedOutputs,
isSecureMode: false,
@@ -732,8 +672,8 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
requestId,
executionId,
workflowId,
workspaceId: workflow.workspaceId,
userId,
workspaceId: workflow.workspaceId ?? undefined,
userId: actorUserId,
triggerType,
useDraftState: shouldUseDraftState,
startTime: new Date().toISOString(),
@@ -808,8 +748,6 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
},
})
} catch (error: any) {
// Block errors are already logged with full details by BlockExecutor
// Only log the error message here to avoid duplicate logging
const errorMessage = error.message || 'Unknown error'
logger.error(`[${requestId}] SSE execution failed: ${errorMessage}`)

View File

@@ -4,6 +4,7 @@ import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { getSession } from '@/lib/auth'
import { env } from '@/lib/env'
import { createLogger } from '@/lib/logs/console/logger'
import { generateRequestId } from '@/lib/utils'
import { extractAndPersistCustomTools } from '@/lib/workflows/custom-tools-persistence'
@@ -248,6 +249,26 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
const elapsed = Date.now() - startTime
logger.info(`[${requestId}] Successfully saved workflow ${workflowId} state in ${elapsed}ms`)
try {
const socketUrl = env.SOCKET_SERVER_URL || 'http://localhost:3002'
const notifyResponse = await fetch(`${socketUrl}/api/workflow-updated`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ workflowId }),
})
if (!notifyResponse.ok) {
logger.warn(
`[${requestId}] Failed to notify Socket.IO server about workflow ${workflowId} update`
)
}
} catch (notificationError) {
logger.warn(
`[${requestId}] Error notifying Socket.IO server about workflow ${workflowId} update`,
notificationError
)
}
return NextResponse.json({ success: true, warnings }, { status: 200 })
} catch (error: any) {
const elapsed = Date.now() - startTime

Some files were not shown because too many files have changed in this diff Show More