Skip to content

Commit b3aa49b

Browse files
authored
feat(n8n): clarification roundtrip route + types (#7316)
Closes the half-built clarification loop on the host side. Previously the plugin populated _meta.requiresClarification, the LLM was told to emit it, but the host route ignored it and the UI never rendered it. POST /api/n8n/workflows/generate now inspects the draft for clarifications before deploying. When non-empty, the route short-circuits and returns: { status: "needs_clarification", draft, clarifications: ClarificationRequest[], catalog: TargetGroup[], } where `catalog` is a snapshot of the connector-target-catalog scoped to the platforms the clarifications reference. The draft itself is returned verbatim so the client can post it back unchanged. POST /api/n8n/workflows/resolve-clarification (new) accepts { draft, resolutions: { paramPath, value }[] }. It applies the resolutions to the draft via a small recursive dot-path setter, prunes the now-resolved structured clarifications from _meta.requiresClarification, and either deploys (when the list is empty) or returns the next pending clarification + updated draft so the UI can chain a second picker (server-then-channel etc.). The path setter (n8n-clarification.ts) supports dot identifiers, bracketed string keys (`nodes["Discord Send"].parameters.channelId`), and numeric indices (`nodes[0].parameters.guildId`). It rejects malformed paths and never overwrites a non-object intermediate. The setter is intentionally tiny — about 30 lines — to avoid pulling lodash into app-core for one feature. Folds in P6: client-types-chat.ts adds N8nClarificationRequest, N8nClarificationTargetGroup, N8nWorkflowNeedsClarificationResponse, N8nWorkflowResolveClarificationRequest, the type-guard isNeedsClarificationResponse, and widens N8nWorkflowGenerateResponse to the three-arm union. The clarification types mirror the plugin's ClarificationRequest shape but live in the host so client code does not import from plugin internals. AutomationsView gets a one-line narrowing branch on isNeedsClarificationResponse so the wider union typechecks cleanly; the actual UI render lands in the next PR in the stack (P5). Tests cover both the helpers and the route handlers: - n8n-clarification.test.ts: 28 cases for coerceClarifications, parseParamPath, setByDotPath, applyResolutions, pruneResolvedClarifications, buildCatalogSnapshot. - n8n-routes.test.ts: appended needs_clarification short-circuit, legacy-string clarification coercion, missing-catalog fallback, resolve happy path, empty-resolutions rejection, malformed-path rejection (with paramPath echo), chained-pending-clarification return path, and missing-draft rejection. Note: the n8n-routes.test.ts file fails to load locally due to a pre-existing missing package (@elizaos/plugin-browser-bridge) in the agent runtime import chain — same failure mode is observable on the unmodified file. The new route cases run cleanly once that dep is restored.
1 parent e4f02fa commit b3aa49b

5 files changed

Lines changed: 1271 additions & 36 deletions

File tree

packages/app-core/src/api/client-types-chat.ts

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,9 +459,74 @@ export interface N8nWorkflowMissingCredentialsResponse {
459459
warning: "missing credentials";
460460
}
461461

462+
/**
463+
* Structured clarification request emitted by the plugin's workflow generator
464+
* when a node parameter cannot be resolved from the runtime context. The host
465+
* surfaces these as quick-pick buttons; on click the host calls
466+
* `/api/n8n/workflows/resolve-clarification` with the chosen value, which
467+
* patches the draft at `paramPath` and deploys — no LLM regeneration.
468+
*
469+
* Mirrors the plugin's `ClarificationRequest` (see
470+
* @elizaos/plugin-n8n-workflow `src/types/index.ts`). Re-declared here to
471+
* avoid a host → plugin import cycle.
472+
*/
473+
export interface N8nClarificationRequest {
474+
kind:
475+
| "target_channel"
476+
| "target_server"
477+
| "recipient"
478+
| "value"
479+
| "free_text";
480+
platform?: string;
481+
scope?: { guildId?: string };
482+
question: string;
483+
paramPath: string;
484+
}
485+
486+
/** One server / workspace / contact-collection from a connector catalog. */
487+
export interface N8nClarificationTargetGroup {
488+
platform: string;
489+
groupId: string;
490+
groupName: string;
491+
targets: Array<{
492+
id: string;
493+
name: string;
494+
kind: "channel" | "recipient" | "chat";
495+
}>;
496+
}
497+
498+
/**
499+
* Returned by `POST /api/n8n/workflows/generate` when the LLM emitted one or
500+
* more `ClarificationRequest`s and the host needs the user to pick a target
501+
* before deploying. The `draft` is the unmodified workflow JSON from the
502+
* plugin (with the unresolved parameters left absent); `catalog` is a
503+
* snapshot of the relevant connector-target-catalog scoped to the
504+
* platforms referenced by the clarifications.
505+
*/
506+
export interface N8nWorkflowNeedsClarificationResponse {
507+
status: "needs_clarification";
508+
draft: Record<string, unknown>;
509+
clarifications: N8nClarificationRequest[];
510+
catalog: N8nClarificationTargetGroup[];
511+
}
512+
513+
/** Resolution payload sent to /api/n8n/workflows/resolve-clarification. */
514+
export interface N8nClarificationResolution {
515+
paramPath: string;
516+
value: string;
517+
}
518+
519+
export interface N8nWorkflowResolveClarificationRequest {
520+
draft: Record<string, unknown>;
521+
resolutions: N8nClarificationResolution[];
522+
name?: string;
523+
workflowId?: string;
524+
}
525+
462526
export type N8nWorkflowGenerateResponse =
463527
| N8nWorkflow
464-
| N8nWorkflowMissingCredentialsResponse;
528+
| N8nWorkflowMissingCredentialsResponse
529+
| N8nWorkflowNeedsClarificationResponse;
465530

466531
export function isMissingCredentialsResponse(
467532
res: N8nWorkflowGenerateResponse,
@@ -473,6 +538,19 @@ export function isMissingCredentialsResponse(
473538
);
474539
}
475540

541+
export function isNeedsClarificationResponse(
542+
res: N8nWorkflowGenerateResponse,
543+
): res is N8nWorkflowNeedsClarificationResponse {
544+
const candidate = res as N8nWorkflowNeedsClarificationResponse;
545+
return (
546+
candidate.status === "needs_clarification" &&
547+
Array.isArray(candidate.clarifications) &&
548+
Array.isArray(candidate.catalog) &&
549+
typeof candidate.draft === "object" &&
550+
candidate.draft !== null
551+
);
552+
}
553+
476554
export interface N8nWorkflowWriteNode {
477555
id?: string;
478556
name: string;

0 commit comments

Comments
 (0)