mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
v0.5.10: copilot upgrade, preprocessor, logs search, UI, code hygiene
This commit is contained in:
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
@@ -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> = {
|
||||
|
||||
36
.github/workflows/ci.yml
vendored
36
.github/workflows/ci.yml
vendored
@@ -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
|
||||
|
||||
6
.github/workflows/docs-embeddings.yml
vendored
6
.github/workflows/docs-embeddings.yml
vendored
@@ -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
|
||||
|
||||
4
.github/workflows/images.yml
vendored
4
.github/workflows/images.yml
vendored
@@ -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:
|
||||
|
||||
55
.github/workflows/trigger-deploy.yml
vendored
55
.github/workflows/trigger-deploy.yml
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -39,14 +39,24 @@ Obtener y filtrar incidencias de Linear
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| `teamId` | string | Sí | ID del equipo de Linear |
|
||||
| `projectId` | string | Sí | 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 | Sí | 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 | Sí | 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 | Sí | 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 | Sí | 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 | Sí | 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 | Sí | 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
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 | いいえ | ステータスの説明 |
|
||||
|
||||
@@ -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 | 否 | 状态描述 |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { inter } from '@/app/fonts/inter/inter'
|
||||
import { inter } from '@/app/_styles/fonts/inter/inter'
|
||||
|
||||
interface LandingTemplatePreviewProps {
|
||||
previewImage: string
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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.',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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(() => {
|
||||
@@ -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(),
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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 })
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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' }
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
}))
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ import {
|
||||
handleTagAndVectorSearch,
|
||||
handleTagOnlySearch,
|
||||
handleVectorOnlySearch,
|
||||
} from './utils'
|
||||
} from '@/app/api/knowledge/search/utils'
|
||||
|
||||
describe('Knowledge Search Utils', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
140
apps/sim/app/api/templates/approved/sanitized/route.ts
Normal file
140
apps/sim/app/api/templates/approved/sanitized/route.ts
Normal 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',
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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}`)
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user