From 7daa5252dbf0de1c7e476386e651a8857676324f Mon Sep 17 00:00:00 2001 From: Saatvik Arya Date: Fri, 24 Apr 2026 20:51:11 +0530 Subject: [PATCH] feat(execution): propagate trigger context from CLI, HTTP, and MCP hosts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flows the trigger: { kind, meta } option the engine added in the previous PR end-to-end so the runs UI can facet by attribution surface. Also promotes recording writes from Effect.ignore to a defect-absorbing variant so a misconfigured storage backend can't take down an execution. Surfaces: - HTTP API (packages/core/api): /executions POST now declares an x-executor-trigger optional header. Handler reads it (defaulting to "http") and passes it as the engine's trigger option. - MCP host (packages/hosts/mcp): explicit trigger: { kind: "mcp" } on engine.execute (inline elicitation path) and engine.executeWithPause (paused flow). - CLI: stamps every /executions call from executeCode with x-executor-trigger: cli. Covers call, search, describe, sources — every subcommand that runs code goes through this helper. Engine robustness: - Introduced silent helper (Effect.catchAllCause(() => Effect.void)) and swapped every bookkeeping .pipe(Effect.ignore) over to it. Effect.ignore only catches typed failures; a synchronous throw inside an adapter (e.g. storage-drizzle when the schema is missing the execution model) becomes a defect and was bypassing ignore. With silent, misconfigured storage just means no row — the execution itself succeeds. Verified by the MCP stdio integration test which previously leaked the [storage-drizzle] unknown model error into the MCP tool result text. Now returns the expected code result. --- apps/cli/src/main.ts | 1 + packages/core/api/src/executions/api.ts | 11 +++++++++++ packages/core/api/src/handlers/executions.ts | 9 +++++++-- packages/hosts/mcp/src/server.ts | 5 ++++- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/apps/cli/src/main.ts b/apps/cli/src/main.ts index 0192820eb..0c786464e 100644 --- a/apps/cli/src/main.ts +++ b/apps/cli/src/main.ts @@ -394,6 +394,7 @@ const executeCode = (input: { payload: { code: input.code, }, + headers: { "x-executor-trigger": "cli" }, }); if (response.status === "paused") { diff --git a/packages/core/api/src/executions/api.ts b/packages/core/api/src/executions/api.ts index 12b557919..d92332435 100644 --- a/packages/core/api/src/executions/api.ts +++ b/packages/core/api/src/executions/api.ts @@ -11,6 +11,16 @@ const ExecuteRequest = Schema.Struct({ code: Schema.String, }); +/** + * Optional header naming the surface that triggered this execution — + * `"cli"`, `"http"`, `"mcp"`, etc. Persisted on the execution row so + * the runs UI can facet by trigger kind. Defaults to `"http"` when + * absent. + */ +const ExecuteHeaders = Schema.Struct({ + "x-executor-trigger": Schema.optional(Schema.String), +}); + const CompletedResult = Schema.Struct({ status: Schema.Literal("completed"), text: Schema.String, @@ -55,6 +65,7 @@ export class ExecutionsApi extends HttpApiGroup.make("executions") .add( HttpApiEndpoint.post("execute")`/executions` .setPayload(ExecuteRequest) + .setHeaders(ExecuteHeaders) .addSuccess(ExecuteResponse), ) .add( diff --git a/packages/core/api/src/handlers/executions.ts b/packages/core/api/src/handlers/executions.ts index 3eedd3e65..82fc2ea39 100644 --- a/packages/core/api/src/handlers/executions.ts +++ b/packages/core/api/src/handlers/executions.ts @@ -8,10 +8,15 @@ import { capture, captureEngineError } from "@executor/api"; export const ExecutionsHandlers = HttpApiBuilder.group(ExecutorApi, "executions", (handlers) => handlers - .handle("execute", ({ payload }) => + .handle("execute", ({ payload, headers }) => capture(Effect.gen(function* () { const engine = yield* ExecutionEngineService; - const outcome = yield* captureEngineError(engine.executeWithPause(payload.code)); + const triggerKind = headers["x-executor-trigger"] ?? "http"; + const outcome = yield* captureEngineError( + engine.executeWithPause(payload.code, { + trigger: { kind: triggerKind }, + }), + ); if (outcome.status === "completed") { const formatted = formatExecuteResult(outcome.result); diff --git a/packages/hosts/mcp/src/server.ts b/packages/hosts/mcp/src/server.ts index 4c30abbb5..894ec9cc0 100644 --- a/packages/hosts/mcp/src/server.ts +++ b/packages/hosts/mcp/src/server.ts @@ -297,10 +297,13 @@ export const createExecutorMcpServer = ( if (supportsManagedElicitation(server)) { const result = yield* engine.execute(code, { onElicitation: makeMcpElicitationHandler(server, debugLog), + trigger: { kind: "mcp" }, }); return toMcpResult(formatExecuteResult(result)); } - const outcome = yield* engine.executeWithPause(code); + const outcome = yield* engine.executeWithPause(code, { + trigger: { kind: "mcp" }, + }); debugLog("execute.paused_flow_result", { status: outcome.status, executionId: outcome.status === "paused" ? outcome.execution.id : undefined,