feat(agent-bff): expose the action execute endpoint with result normalization#1752
Conversation
6 new issues
|
…eld error Carry the native action Error result's html on ActionFormValidationError and throw a typed UnknownActionFieldError from the strict setFields, so consumers can render error markup and route unknown fields to a client error. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lization Serve POST /v1/:collection/actions/:action/execute: reject unknown submitted fields via setFields, run the action, and normalize the agent result into a flat wrapper (success/error/webhook/redirect), returning 501 for File results. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Validate value shapes (not just key presence) when normalizing the execute result so malformed payloads fall through to 501 instead of a null-field 200, and keep an empty roleIdsAllowedToApprove array in the approval 403 details. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
911b16e to
c4f48d5
Compare
Review — key pointsMulti-perspective review (correctness, tests, error-handling, types, comments, simplification). No Critical/blocking bugs. Verified the mapper's wire-shape discrimination ( Missing test coverage (worth adding)
Advisory — worth a conscious decision before merge
🤖 Generated with Claude Code |
… fall-through Add the setFields agent-error fallback, approval-without-roles, error-body html-absent, and non-array refresh.relationships cases; log the unrecognized payload keys when execute maps to 501; correct the mapper rationale comment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Addressed in 308d861. Test coverage (1–4): all four branches now covered — 501 fall-through: added a Mapper comment: corrected — the real driver is Kept — flat error body shape ( |
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Coverage Impact ⬆️ Merging this pull request will increase total coverage on Modified Files with Diff Coverage (6)
🛟 Help
|

What
Exposes
POST /v1/:collection/actions/:action/executeon the BFF, stacked on the action-form PR (#1748). It rejects unknown submitted fields, runs the Smart Action, and normalizes the agent result into one flat wrapper — success / error / webhook / redirect — returning a structured 501 for File results, which are unimplemented in v1.Fixes PRD-674
How
agent-client(2 additive, backward-compatible changes — a version bump ships with this, so rollback is no longer "redeploy agent-bff alone"):ActionFormValidationErrornow carrieshtml;toActionErrorforwards it from the native actionErrorpayload ({ error, html }). Without this the html is dropped before reaching the BFF.setFieldsthrows a typedUnknownActionFieldError(same message as before) so callers route an unknown field to a client error instead of a generic 500. Existing consumers (mcp-server, workflow-executor) catch generically and are unaffected.agent-bff:action-execute-mapper.ts— pure mapper: the agent 200 payload is discriminated positively (webhook / redirect / success); any unrecognized body (a streamed File) falls through to a501 unsupported_action_result.HttpRequesterdoes not expose response headers, soContent-Dispositioncannot be read — the fall-through is the only viable File detection.agent-action-client.ts— a singleloadActionmethod (form and execute load the same agent-clientActionobject);Action extends ActionForm.action-routes-middleware.ts— one middleware matching both/formand/executewith shared body parsing and action resolution.execute()gets its own try/catch (it cannot use the genericcallAgent, which would mislabel the action-Error 400 as a transport 502):UnknownActionFieldError→ 400invalid_request,ActionFormValidationError→ 400{ type: "error", ..., html },ActionRequiresApprovalError→ 403action_requires_approval, transport 5xx →agent_unavailable.bff-local-errors.ts— newactionRequiresApproval(403).Result shapes
{ type: "success", message, invalidated: [...], html }{ type: "error", status: 400, message, html }{ type: "webhook", url, method, headers, body }{ type: "redirect", path }{ error: { type: "unsupported_action_result", status: 501 } }Out of scope / follow-ups
createApprovalRequest); they surface as403 action_requires_approval(withroleIdsAllowedToApprove). Full wiring depends on the auth foundation (PRD-637) providing a per-mode Forest-server token + rendering context, and would add anapprovalRequestedwrapper shape. To confirm with the spec author.action_not_allowed(403) still has no local trigger — every non-exposed action maps to 404, mirroring the collection/relation routes (existing TODO).Tests
agent-client: html carry on the error path, typedUnknownActionFieldErrorfromsetFields.agent-bff: pure execute mapper (all shapes + 501), the/executeroute (missing recordIds, unknown field, action Error + html, approval, File, unknown action, transport 5xx vs action-Error 400, success/webhook/redirect), and the sharedloadAction.setFieldsconsumers: mcp-server (24) and workflow-executor adapter (63) green.🤖 Generated with Claude Code
Note
Expose action execute endpoint in agent-bff with result normalization
/agent/v1/:collection/actions/:action/executeroute in action-routes-middleware.ts that callssetFieldsthenexecuteon the action, dispatching tohandleExecutealongside the existinghandleFormhandler.webhook,redirect, orsuccess(withinvalidated/html); unrecognized shapes return a structured 501unsupported_action_result.UnknownActionFieldError→ 400invalid_request,ActionFormValidationError→ 400 with optionalhtml,ActionRequiresApprovalError→ 403action_requires_approvalwith optionalroleIdsAllowedToApprove.loadActionFormtoloadActionin agent-action-client.ts and extends the returnedActioninterface withsetFieldsandexecute.agent-client,setFieldsnow throwsUnknownActionFieldErrorinstead of a genericErrorfor unknown fields, andActionFormValidationErrornow carries an optionalhtmlproperty forwarded from 400/422 responses.Macroscope summarized 6763260.