Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/engine/src/lib/operations/flow.operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ export const flowOperation = {
const input = operation as ExecuteFlowOperation
const constants = EngineConstants.fromExecuteFlowInput(input)
const output: FlowExecutorContext = (await executieSingleStepOrFlowOperation(input)).finishExecution()
console.log('[FlowOperation] Flow execution finished, calling explicit final backup')
await progressService.backup({
engineConstants: constants,
flowExecutorContext: output,
})
console.log('[FlowOperation] Explicit final backup completed')
return {
status: EngineResponseStatus.OK,
response: undefined,
Expand Down
66 changes: 48 additions & 18 deletions packages/engine/src/lib/services/progress.service.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { setTimeout } from 'timers/promises'
import { OutputContext } from '@activepieces/pieces-framework'
import { DEFAULT_MCP_DATA, EngineGenericError, EngineSocketEvent, FlowActionType, FlowRunStatus, GenericStepOutput, isFlowRunStateTerminal, isNil, logSerializer, RunEnvironment, StepOutput, StepOutputStatus, StepRunResponse, UpdateRunProgressRequest, UploadRunLogsRequest } from '@activepieces/shared'
import { Mutex } from 'async-mutex'
Expand All @@ -15,20 +16,39 @@ const fetchWithRetry = fetchRetry(global.fetch)

const BACKUP_INTERVAL_MS = 15000
export let latestUpdateParams: UpdateStepProgressParams | null = null
let backupIntervalId: NodeJS.Timeout | null = null
let backupController: AbortController | null = null
let backupLoopPromise: Promise<void> | null = null

async function backupLoop(signal: AbortSignal): Promise<void> {
while (!signal.aborted) {
try {
if (latestUpdateParams) {
console.log('[Progress] Backup interval fired, starting backup')
await progressService.backup(latestUpdateParams)
console.log('[Progress] Backup interval completed')
}
}
catch (err) {
console.error('[Progress] Backup failed', err)
}

// Sleep for interval or until aborted
try {
await setTimeout(BACKUP_INTERVAL_MS, undefined, { signal })
}
catch {
// sleep aborted → loop will exit naturally
}
}
}

export const progressService = {
init: (): void => {
if (backupIntervalId) {
clearInterval(backupIntervalId)
if (backupController) {
return
}

backupIntervalId = setInterval(async () => {
if (isNil(latestUpdateParams)) {
return
}
await progressService.backup(latestUpdateParams)
}, BACKUP_INTERVAL_MS)
backupController = new AbortController()
backupLoopPromise = backupLoop(backupController.signal)
},
sendUpdate: async (params: UpdateStepProgressParams): Promise<void> => {
return updateLock.runExclusive(async () => {
Expand Down Expand Up @@ -89,8 +109,7 @@ export const progressService = {
return
}
await lock.runExclusive(async () => {
const params = updateParams
const { flowExecutorContext, engineConstants } = params!
const { flowExecutorContext, engineConstants } = updateParams
const executionState = await logSerializer.serialize({
executionState: {
steps: flowExecutorContext.steps,
Expand Down Expand Up @@ -135,17 +154,28 @@ export const progressService = {
await sendLogsUpdate(request)
})
},
shutdown: () => {
if (backupIntervalId) {
clearInterval(backupIntervalId)
backupIntervalId = null
shutdown: async () => {
if (!backupController) {
return
}

console.log('[Progress] Shutdown called, stopping backup loop')
backupController.abort()

if (backupLoopPromise) {
console.log('[Progress] Waiting for in-progress backup to complete')
await backupLoopPromise
}

backupController = null
backupLoopPromise = null
latestUpdateParams = null
console.log('[Progress] Shutdown complete')
},
}

process.on('SIGTERM', progressService.shutdown)
process.on('SIGINT', progressService.shutdown)
process.on('SIGTERM', () => void progressService.shutdown())
process.on('SIGINT', () => void progressService.shutdown())

type CreateOutputContextParams = {
engineConstants: EngineConstants
Expand Down
2 changes: 1 addition & 1 deletion packages/engine/src/lib/worker-socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ let socket: Socket | undefined
async function executeFromSocket(operation: EngineOperation, operationType: EngineOperationType): Promise<void> {
const result = await execute(operationType, operation)
const resultParsed = JSON.parse(JSON.stringify(result))
progressService.shutdown()
await progressService.shutdown()
await workerSocket.sendToWorkerWithAck(EngineSocketEvent.ENGINE_RESPONSE, resultParsed)
}

Expand Down
2 changes: 1 addition & 1 deletion packages/pieces/community/kapso/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@activepieces/piece-kapso",
"version": "0.0.1",
"version": "0.0.2",
"type": "commonjs",
"main": "./src/index.js",
"types": "./src/index.d.ts",
Expand Down
170 changes: 170 additions & 0 deletions packages/pieces/community/kapso/src/i18n/de.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
{
"Send and receive WhatsApp messages, media, templates, and more using the Kapso WhatsApp API.": "Senden und empfangen Sie WhatsApp Nachrichten, Medien, Vorlagen und mehr mit der Kapso WhatsApp API.",
"Your Kapso API key. You can obtain it from your [Kapso dashboard](https://app.kapso.ai).": "Dein Kapso API-Schlüssel. Du kannst ihn von deinem [Kapso Dashboard](https://app.kapso.ai).",
"Send Text Message": "Textnachricht senden",
"Send Button Message": "Senden-Button Nachricht",
"Send List Message": "Listennachricht senden",
"Send Image": "Bild senden",
"Send Video": "Video senden",
"Send Audio": "Audio senden",
"Send Document": "Dokument senden",
"Send Sticker": "Sticker senden",
"Send Location": "Standort senden",
"Request User Location": "Standort anfordern",
"Send Contact": "Kontakt senden",
"Send Reaction": "Antwort senden",
"Mark Message as Read": "Nachricht als gelesen markieren",
"Send Template Message": "Vorlagennachricht senden",
"Custom API Call": "Eigener API-Aufruf",
"Send a text message via WhatsApp.": "Senden Sie eine Textnachricht über WhatsApp.",
"Send an interactive button message via WhatsApp (up to 3 buttons).": "Senden Sie eine interaktive Schaltflächenmitteilung über WhatsApp (bis zu 3 Schaltflächen).",
"Send an interactive list message via WhatsApp.": "Eine interaktive Listennachricht über WhatsApp senden.",
"Send an image message via WhatsApp.": "Senden Sie eine Bildnachricht über WhatsApp.",
"Send a video message via WhatsApp.": "Senden Sie eine Video-Nachricht über WhatsApp.",
"Send an audio message via WhatsApp.": "Senden Sie eine Audio-Nachricht über WhatsApp.",
"Send a document message via WhatsApp.": "Eine Dokumentennachricht über WhatsApp senden.",
"Send a sticker message via WhatsApp.": "Eine Aufkleber-Nachricht über WhatsApp senden.",
"Send a location message via WhatsApp.": "Eine Ortungsnachricht über WhatsApp senden.",
"Send a location request message to a WhatsApp user.": "Eine Ortungsanfrage an einen WhatsApp Benutzer senden.",
"Send a contact card via WhatsApp.": "Eine Kontaktkarte über WhatsApp senden.",
"React to a WhatsApp message with an emoji.": "Reagiere auf eine WhatsApp Nachricht mit einem Emoji.",
"Mark a WhatsApp message as read.": "Eine WhatsApp Nachricht als gelesen markieren.",
"Send a pre-approved WhatsApp template message.": "Senden Sie eine vorab genehmigte WhatsApp Vorlage Nachricht.",
"Make a custom API call to a specific endpoint": "Einen benutzerdefinierten API-Aufruf an einen bestimmten Endpunkt machen",
"Phone Number": "Telefonnummer",
"Recipient Phone Number": "Telefonnummer des Empfängers",
"Message": "Nachricht",
"Preview URL": "URL Vorschau",
"Body Text": "Body-Text",
"Buttons": "Tasten",
"Footer Text": "Fußzeilentext",
"Button Text": "Schaltflächentext",
"Sections": "Abschnitte",
"Header Text": "Kopfzeile Text",
"Image URL": "Bild-URL",
"Image Media ID": "Bild-Medien-ID",
"Caption": "Überschrift",
"Video URL": "Video-URL",
"Video Media ID": "Video-Medien-ID",
"Audio URL": "Audio-URL",
"Audio Media ID": "Audio-Medien-ID",
"Document URL": "URL des Dokuments",
"Document Media ID": "Dokumenten-Medien-ID",
"Filename": "Dateiname",
"Sticker URL": "Sticker URL",
"Sticker Media ID": "Aufklebermedien-ID",
"Latitude": "Breitengrad",
"Longitude": "Längengrad",
"Location Name": "Standortname",
"Address": "Adresse",
"Full Name": "Voller Name",
"First Name": "Vorname",
"Last Name": "Nachname",
"Email": "E-Mail",
"Company": "Firma",
"Message ID": "Nachrichten-ID",
"Emoji": "Emoji",
"Business Account": "Geschäftskonto",
"Template": "Vorlage",
"Header Type": "Header-Typ",
"Header Text Parameters": "Kopfzeilentext-Parameter",
"Header Media URL": "Kopfzeilenmedien-URL",
"Header Document Filename": "Dateiname des Kopfzeilendokuments",
"Header Location Latitude": "Breite der Kopfzeile",
"Header Location Longitude": "Längengrad der Kopfzeile",
"Header Location Name": "Name der Kopfzeile",
"Header Location Address": "Adresse der Kopfzeile",
"Body Parameters": "Körperparameter",
"Button Parameters": "Tastenparameter",
"Method": "Methode",
"Headers": "Kopfzeilen",
"Query Parameters": "Abfrageparameter",
"Body Type": "Körpertyp",
"Body": "Körper",
"Response is Binary ?": "Antwort ist binär?",
"No Error on Failure": "Kein Fehler bei Fehler",
"Timeout (in seconds)": "Timeout (in Sekunden)",
"Follow redirects": "Weiterleitungen folgen",
"Select the WhatsApp phone number to send from.": "Wählen Sie die zu sendende WhatsApp Telefonnummer aus.",
"The recipient's phone number in international format (e.g. 15551234567).": "Die Telefonnummer des Empfängers im internationalen Format (z.B. 15551234567).",
"The text message to send.": "Die zu sendende Textnachricht.",
"Whether to show a link preview if the message contains a URL.": "Gibt an, ob eine Link-Vorschau angezeigt werden soll, wenn die Nachricht eine URL enthält.",
"The message body displayed above the buttons.": "Der Nachrichtenkörper, der über den Schaltflächen angezeigt wird.",
"Up to 3 buttons. Each button needs an ID and a title (max 20 characters).": "Bis zu 3 Schaltflächen. Jede Taste benötigt eine ID und einen Titel (max. 20 Zeichen).",
"Optional footer text displayed below the buttons.": "Optionaler Fußzeilentext, der unter den Schaltflächen angezeigt wird.",
"The message body displayed above the list.": "Der Nachrichtenkörper, der oberhalb der Liste angezeigt wird.",
"The text on the button that opens the list (max 20 characters).": "Der Text auf der Schaltfläche, die die Liste öffnet (max. 20 Zeichen).",
"List sections. Each section contains a title and rows.": "Sektionen auflisten. Jeder Abschnitt enthält einen Titel und Zeilen.",
"Optional header text.": "Optionaler Kopfzeilen-Text.",
"Optional footer text.": "Optionaler Fußzeilentext.",
"Public URL of the image to send.": "Öffentliche URL des zu sendenden Bildes.",
"Media ID of a previously uploaded image. Use either URL or Media ID.": "Medien-ID eines zuvor hochgeladenen Bildes. Verwenden Sie entweder URL oder Medien-ID.",
"Optional caption for the image.": "Optionale Bildunterschrift für das Bild.",
"Public URL of the video to send.": "Öffentliche URL des zu sendenden Videos.",
"Media ID of a previously uploaded video. Use either URL or Media ID.": "Medien-ID eines zuvor hochgeladenen Videos. Verwenden Sie entweder URL oder Medien-ID.",
"Optional caption for the video.": "Optionale Beschriftung für das Video.",
"Public URL of the audio file to send.": "Öffentliche URL der zu sendenden Audiodatei.",
"Media ID of a previously uploaded audio. Use either URL or Media ID.": "Medien-ID eines zuvor hochgeladenen Audios. Verwenden Sie entweder URL oder Medien-ID.",
"Public URL of the document to send.": "Öffentliche URL des zu sendenden Dokuments.",
"Media ID of a previously uploaded document. Use either URL or Media ID.": "Medien-ID eines zuvor hochgeladenen Dokuments. Verwenden Sie entweder URL oder Medien-ID.",
"The filename to display for the document.": "Der Dateiname für das Dokument.",
"Optional caption for the document.": "Optionale Unterschrift für das Dokument.",
"Public URL of the sticker (WebP format).": "Öffentliche URL des Aufklebers (WebP-Format).",
"Media ID of a previously uploaded sticker. Use either URL or Media ID.": "Medien-ID eines zuvor hochgeladenen Aufklebers. Verwenden Sie entweder URL oder Medien-ID.",
"Latitude of the location.": "Breite des Ortes.",
"Longitude of the location.": "Längengrad des Ortes.",
"Name of the location.": "Name des Ortes.",
"Address of the location.": "Adresse des Ortes.",
"The message body displayed with the location request.": "Der Nachrichtenkörper, der mit der Standortanfrage angezeigt wird.",
"The contact's formatted full name.": "Der vollständige Name des Kontakts formatiert.",
"The contact's first name.": "Der Vorname des Kontakts.",
"The contact's last name.": "Nachname des Kontakts.",
"The contact's phone number.": "Die Telefonnummer des Kontakts.",
"The contact's email address.": "Die E-Mail-Adresse des Kontakts.",
"The contact's company name.": "Der Firmenname des Kontakts.",
"The ID of the message to react to.": "Die ID der Nachricht auf die reagiert werden soll.",
"The emoji to react with (e.g. 👍). Leave empty to remove a reaction.": "Der Emoji zu reagieren (z.B. 👍). Leer lassen um eine Reaktion zu entfernen.",
"The ID of the message to mark as read.": "Die ID der Nachricht die als gelesen markiert werden soll.",
"Select the WhatsApp Business Account.": "Wählen Sie das WhatsApp Business-Konto.",
"Select a message template to send.": "Wählen Sie eine Nachrichtenvorlage zum Senden aus.",
"The type of header your template uses. Leave as None if no header.": "Der Header-Typ, den Ihre Vorlage verwendet. Lassen Sie als Keine, wenn kein Header.",
"Text header parameters (only if header type is Text).": "Parameter des Textkopfes (nur wenn der Headertyp Text ist).",
"Public URL of the media file for image, video, or document headers.": "Öffentliche URL der Mediendatei für Bild-, Video- oder Dokumenten-Header.",
"Filename for document headers (e.g. invoice.pdf).": "Dateiname für Dokumentheader (z.B. invoice.pdf).",
"Latitude for location headers.": "Breite für Standort-Kopfzeilen.",
"Longitude for location headers.": "Längengrad für Standort-Header.",
"Name of the location (e.g. Delivery Location).": "Name des Standortes (z.B. Lieferort).",
"Template body parameters. Each entry needs a parameter name and value.": "Template-Body Parameter. Jeder Eintrag benötigt einen Parameternamen und Wert.",
"Template button parameters. Each entry maps to a button by index.": "Parameter der Template-Schaltfläche. Jeder Eintrag wird nach Index auf eine Schaltfläche zugeordnet.",
"Authorization headers are injected automatically from your connection.": "Autorisierungs-Header werden automatisch von Ihrer Verbindung injiziert.",
"Enable for files like PDFs, images, etc.": "Aktivieren für Dateien wie PDFs, Bilder usw.",
"None": "Keine",
"Text": "Text",
"Image": "Bild",
"Video": "Video",
"Document": "Dokument",
"Location": "Standort",
"GET": "ERHALTEN",
"POST": "POST",
"PATCH": "PATCH",
"PUT": "PUT",
"DELETE": "LÖSCHEN",
"HEAD": "HEAD",
"JSON": "JSON",
"Form Data": "Formulardaten",
"Raw": "Rohe",
"New Message Received": "Neue Nachricht empfangen",
"Message Status Update": "Nachrichtenstatus-Update",
"Triggers when a new WhatsApp message is received.": "Löst aus, wenn eine neue WhatsApp-Nachricht empfangen wird.",
"Triggers when a message status changes (sent, delivered, read).": "Wird ausgelöst, wenn sich der Nachrichtenstatus ändert (gesendet, ausgeliefert, gelesen).",
"Markdown": "Markdown",
"Status Filter": "Statusfilter",
"**Setup Instructions:**\n\n1. Copy the **Webhook URL** below.\n2. Go to your [Kapso dashboard](https://app.kapso.ai) and open your WhatsApp number settings.\n3. Paste the URL as your **Webhook destination URL**.\n4. Incoming WhatsApp events will now trigger this flow.\n\n**Webhook URL:**\n```text\n{{webhookUrl}}\n```\n": "**Installationsanweisungen:**\n\n1. Kopieren Sie die **Webhook URL** unten.\n2. Gehe zu deinem [Kapso Dashboard](https://app.kapso.ai) und öffne deine WhatsApp Nummer-Einstellungen.\n3. Fügen Sie die URL als **Webhook Ziel-URL** ein.\n4. Eingehende WhatsApp-Ereignisse lösen nun diesen Fluss aus.\n\n**Webhook URL:**\n```text\n{{webhookUrl}}\n```\n",
"**Setup Instructions:**\n\n1. Copy the **Webhook URL** below.\n2. Go to your [Kapso dashboard](https://app.kapso.ai) and open your WhatsApp number settings.\n3. Paste the URL as your **Webhook destination URL**.\n4. Message status updates will now trigger this flow.\n\n**Webhook URL:**\n```text\n{{webhookUrl}}\n```\n": "**Installationsanweisungen:**\n\n1. Kopieren Sie die **Webhook URL** unten.\n2. Gehe zu deinem [Kapso Dashboard](https://app.kapso.ai) und öffne deine WhatsApp Nummer-Einstellungen.\n3. Fügen Sie die URL als **Webhook Ziel-URL** ein.\n4. Nachrichten Statusaktualisierungen lösen nun diesen Fluss aus.\n\n**Webhook URL:**\n```text\n{{webhookUrl}}\n```\n",
"Only trigger for a specific status. Leave as All to trigger for any status change.": "Wird nur für einen bestimmten Status ausgelöst. Lassen Sie als Alle für eine Statusänderung auslösen.",
"All": "Alle",
"Sent": "Gesendet",
"Delivered": "Ausgestellt",
"Read": "Lesen",
"Failed": "Fehlgeschlagen"
}
Loading
Loading