Developers
Connectors and sidebar apps (Cliedesk Pro)
This guide covers a feature of Cliedesk Pro. The free plugin is a complete help desk; see what Pro adds.
Module pro_connectors (cliedesk-pro/src/Modules/Connectors/, UI in cliedesk-pro/app/connectors/). It covers the
HTTP connector builder, response mapping, test mode and health, templates and
suggestions, “call connector” sidebar buttons and iframe sidebar apps with the plugin
panels. AI-built panels are the module pro_ai_panels (see below).
One Action per connector
Every saved connector is the Action connector.{slug} (ConnectorAction). Its input schema and flags are read from
the saved connector each time, so editing a connector needs no new registration:
| Flag | Value |
|---|---|
automation |
true: rules and macros can run it |
ai_tool |
true for read connectors (the AI tool registry, M5/M6, reads it); false for write connectors |
dry_run_supported |
true: a dry run uses the connector’s test settings (mock or staging), never production |
| capability | cliedesk_view_conversations (read), cliedesk_edit_conversation_fields (write) |
Input: conversationId or contactId (the subject, scope-checked), via (agent, macro, button, panel, portal,
test) and one property per agent or customer input. Conversation, contact and fixed inputs are not in
the schema: the server always fills them (only an admin’s preset, a button default or a test sample, replaces
them). Result: ok, outputs, explanation, mapped, card and
variables (the scalar outputs; rules use them as {vars.name}).
People, rules (each active connector is in cliedesk_automation_actions and cliedesk_automation_contact_actions),
macros (step {type: "connector", action: "connector.{slug}", input: "{JSON}"}), sidebar buttons, sidebar cards and
later the AI agent and portal all run this Action, so the checks and the run log are the same everywhere.
Definition
The definition is JSON data, checked by Definition::normalize():
inputs[]:{name, label, type: text|number|bool, source: {kind, key?, value?}, required, description, format}. Kinds:conversation(keyid,number,subject,status,priorityorfield:{key}),contact(id,name,email,phone,wp_user_idorfield:{key}),fixed,agent(typed by a person),customer(“AI collects from customer”, stored now; the AI agent fills it later).format: "email"(and everycontact.emailinput) accepts only a plain address: no quotes, backslashes, brackets or spaces, so the value cannot change a search expression of the service (Stripequery, Klaviyofilter).steps[](at most 10):{id, type: "request", method, url, headers[], body, bodyType: json|form|none, when}or{id, type: "action", action, input{}, when}.whenis{left, op, right}with opeq,ne,contains,empty,not_empty,gt,lt.- Tokens:
{inputs.name},{outputs.name}(explanation),{steps.<id>.status},{steps.<id>.body.a.b.0}. Values are URL-encoded in addresses and JSON-escaped in JSON bodies. Tokens may fill the path and query of a step URL, never its host or port. A filled URL with a.or..path segment fails withconnector_bad_url. outputs[]:{name, path (JSONPath), step?, type: text|number|bool|list}. JSONPath subset:$,.name,['name'],[0],[-1],[*],.*(no filters or scripts).explanation,auth(none,bearer,basicwithusername,header/querywithnameandprefix),timeout(default 15, max 60),confirm,mapping[],card.title,test(mode,stagingUrl,mock,mocks{stepId},sample{}).
Every save writes a version to Free’s config_versions (object type pro_connector); connectors.restore
saves an older version as a new one.
Security
- Requests go out only through Free’s
SafeHttp(HTTPS, public addresses only, DNS pinned, no redirects, 1 MB responses). Private-network addresses fail withconnector_blocked. - The credential is a vault row
pro_connector.{uid}.auth(kindconnector_auth); it is never returned, never in the definition and never logged. The credentials page shows where it is used (cliedesk_secret_usage). - The credential is bound to the definition saved when it was entered: the request hosts and the auth type with its
header or parameter name (option
cliedesk_pro_connector_bindings, uid => binding). A run, a test with an unsaved definition, a change or a restore that moves the secret to another host or place fails withconnector_credential_unbound(422) until the credential is entered again. A secret saved before the binding existed is bound to the definition saved at the next change or run. - Staging tests never get the live secret: they use the staging secret (
PUT /connectors/{id}/secretwithstaging: true, vault rowpro_connector.{uid}.staging) or no credential at all. connectors.testwith an unsaved definition in live or staging mode is audited.connectors.runrefuses a connector withconfirmunless the request sendsconfirm: true.- A connector that a sidebar button restricts (roles, a locked value, a confirmation) runs for people only through
the button (
connectors.run,connector.{slug}, panels and the AI get 403), unless they havecliedesk_manage_integrations; rules still run it.connectors.availableleaves such connectors out for others and never returns fixed input values. - Mapped values are written only with edit rights: conversation fields need
cliedesk_edit_conversation_fields, contact fields and custom recordscliedesk_edit_contacts. A sidebar card run (via = panel) never writes. - The run log stores no request or response bodies.
Mapping and the connector source
Mapped field values are written with Fields::set_values() and field_values.source = 10 (Mapping:: SOURCE_CONNECTOR) with source_ref = connector:{id}. Free’s codes are 1–9; Free shows code 10 as “unknown” in
field provenance until it names it. Transforms: date (PHP date format, default Y-m-d; epoch seconds or
milliseconds), money (currency, minor for minor units), first.
Run log, health and jobs
Table cliedesk_pro_connector_logs: one row per run (via, mode, ok, HTTP status, error, duration, conversation,
contact, actor). Failed runs inside another transaction are kept (Logs::write_durable()).
| Job | When | What |
|---|---|---|
cliedesk_pro_connector_logs_purge |
daily | deletes runs older than pro_connectors.log_days (30) |
cliedesk_pro_connector_spikes |
hourly | notifies admins (cliedesk_notify, kind connector_errors) when at least spike_min_errors (5) runs and spike_error_pct (50%) failed in the last hour; once per 6 hours per connector |
cliedesk_pro_connector_suggestions |
weekly | keyword scan of unresolved conversations of the last 30 days |
Templates and suggestions
Templates::all() (filter cliedesk_pro_connector_templates): stripe_customer, aftership, statuspage,
hubspot_contact, licence_server (License Manager for WooCommerce; parameter base_url) and klaviyo_profile.
Each has a mock response, so it passes the test console at once, and keywords for the suggestion scan. The scan is
keyword based; AI-ranked suggestions come with the AI gateway (M5).
Sidebar buttons and apps
- Buttons (setting
pro_connectors.buttons): a native context panelpro_connector_buttons(“Actions”) with one card form per button. Each button is the registered rule actionext.pcb_{key}(kept out of the rule editor). - Apps (setting
pro_connectors.apps): HTTPS pages on another origin. Bridge Actions:panel_apps.for,panel_apps.context(getContext) andpanel_apps.run(runAction, only the app’s listed actions, audited).panel_apps.runfillsconversationId,contactIdand a subjectidfrom the record on screen; an input that names another conversation or contact is refused (403). App origins drop the default port, and an app may not use the origin of the home, site or admin address. The JS side (app/connectors/bridge.ts,AppFrame.tsx) usespostMessagewith origin and source checks and a sandboxed iframe:
parent.postMessage( { cliedesk: 1, id: 'r1', type: 'getContext' }, '*' );
parent.postMessage( { cliedesk: 1, id: 'r2', type: 'runAction', action: 'conversation.priority.set', input: { priority: 'high' } }, '*' );
parent.postMessage( { cliedesk: 1, type: 'resize', height: 420 }, '*' );
// Replies: { cliedesk: 1, id, ok: true, data } or { cliedesk: 1, id, ok: false, error }, posted to the app's origin.
The app fill is registered on the slot context.apps (props entity, entityId). Free’s AppPanels renders this
slot after the native panels, in the conversation sidebar and on the contact page (M11).
- Plugin panels (
Panels\Adapters), registered when the plugin is detected: YITH Points and Rewards (ywpar_get_customer()), WPLoyalty (Wlr\App\Helpers\Base::getPointUserByEmail()), MailPoet (MailPoet\API\API::MP( 'v1' )), FluentCRM (FluentCrmApi( 'contacts' )), Klaviyo (the connector named inpro_connectors.klaviyo_connector) and WooCommerce product reviews. The email based panels use only verified identities: the linked WordPress user, or an email identity withverified_at. - Suggestions show as evidence only the conversations the reader may see.
AI-built panels
Module pro_ai_panels (Connectors/Panels/AiPanelsModule.php, depends on pro_connectors and pro_ai; UI
app/connectors/AiPanelsSection.tsx, section Settings → Developer → AI-built panels). Tests:
tests/phpunit/pro/connectors/AiPanelsTest.php, cliedesk-pro/app/__tests__/AiPanels.test.tsx.
- Definition (
PanelDefinition, JSON data):title,badge {text, tone},places,outputs[]({name, path, type, transform, format, currency, minor}: the panel’s own JSONPath over the connector’s last response),blocks[](text,fields,list {source},image,buttonswith{label, url}or{button: key}),empty,linkHosts. Tokens{outputs.name}fill texts, field values and links. Rendered into Free’s card schema and checked withCards::check(). - Safety: no forms or free actions (a
buttonitem is an existing sidebar button, rendered with its own form, roles and confirmation); links and images only on the connector’s request hosts orlinkHosts; a link that is a single token must fill to https on one of those hosts; tokens in a fixed link are URL-encoded. The AI may add alinkHostsentry only when the admin named that host in the description. An AI draft is checked leniently (a part that is not allowed is dropped and listed ingeneration.warnings), an admin’s save strictly (field errors). - Source connector: active,
read, and no required agent or customer input (connector_panel_source, 422). - AI draft (
PanelGenerator): (taskreason), promptconnector_panels.generatewith structured output. The description is the instruction; the connector description and the sample response areuntrustedparts.AiGateway::submit();cliedesk_ai_run_completed(optionconnector_panel) saves the checked answer as the asker’s draft. Whenblocked()is not null,generateanswers 409cliedesk_pro_ai_unavailableand the admin writes the panel by hand. - Versions: table
cliedesk_pro_connector_panels(published definition,tested_hash,generation); every version is in Free’sconfig_versions(object typepro_connector_panel). A draft is the editor’s draft version (origin: ai|humanin the snapshot). A draft is published only after a passing test of that exact definition (connector_panel_not_tested, 409). Publishing writes a live version (AI drafts:author_typeAI,author_ref“admin copilot”,approved_bythe admin) and is audited (connector_panels.publish). Rollback publishes a version that was live before (connector_panel_not_published, 409 otherwise) and is audited. - Sidebar: each published panel is the context panel
aip_…; it runsconnector.{slug}as the viewer withvia = panel(scope, button restrictions and the run log as everywhere) and reads the response throughService::capture(). The response body never goes into the Action result. Drafts are never rendered for agents.
Routes (cliedesk_manage_integrations): GET|POST /connector-panels, GET /connector-panels/{id},
POST /connector-panels/generate ({connectorId | id, name?, description, sample?} → {panel, aiRunUid, pollUrl}), POST /connector-panels/preview ({connectorId, definition, sample}), POST /connector-panels/{id}/draft,
POST /connector-panels/{id}/test ({version? | definition?, mode: mock|staging, conversationId?, contactId?}),
POST /connector-panels/{id}/publish|rollback ({version}), POST /connector-panels/{id}/unpublish.
REST routes (cliedesk/v1)
GET|POST /connectors, GET|PATCH /connectors/{id}, POST /connectors/{id}/archive|restore|test|run,
PUT /connectors/{id}/secret, GET /connectors/{id}/versions|health, GET /connectors/available|vocabulary,
GET /connector-templates, POST /connector-templates/{key}/install, GET /connector-suggestions,
POST /connector-suggestions/scan, POST /connector-suggestions/{template}/dismiss, GET|PUT /connector-buttons,
GET|PUT /panel-apps, GET /panel-apps/for|context, POST /panel-apps/run.