import { HttpApiBuilder } from "effect/unstable/httpapi";import { Effect } from "effect";import { Schema } from "effect"; import { ExecutorApi } from "../api";import { formatExecuteResult, formatPausedExecution } from "@executor-js/execution";import { resolveArtifactAction } from "@executor-js/host-mcp/artifact-action";import { TOOL_CALL_CONTRACT_MESSAGE } from "@executor-js/host-mcp/tool-call-code";import { PENDING_APPROVAL_TTL_MS } from "@executor-js/sdk";import { ExecutionEngineService, ExecutorService } from "../services";import { capture, captureEngineError } from "@executor-js/api"; class ExecutionNotFoundError extends Schema.TaggedErrorClass<ExecutionNotFoundError>()( "ExecutionNotFoundError", { executionId: Schema.String, },) {} /** * An artifact-originated execution that could not be resolved into a call. * * Carries the same vocabulary the MCP host's `execute-action` returns * (`invalid_action_code`, `artifact_unavailable`, `binding_unresolved`) so the * shell sees one contract whichever transport it reached the server through, * and the binding UI that ships with sharing can key off `role`/`integration`. */class ArtifactActionError extends Schema.TaggedErrorClass<ArtifactActionError>()( "ArtifactActionError", { error: Schema.Literals(["invalid_action_code", "artifact_unavailable", "binding_unresolved"]), reason: Schema.String, role: Schema.optional(Schema.String), integration: Schema.optional(Schema.String), }, { httpApiStatus: 400 },) { override get message(): string { return this.reason; }} /** * An approval that expired before the human answered. * * Distinct from `ExecutionNotFoundError` (an id that was never ours) so the * shell can tell the user their approval window closed and the action can simply * be triggered again — nothing ran. 410 Gone, because the resource existed and * deliberately no longer does. */class ApprovalExpiredError extends Schema.TaggedErrorClass<ApprovalExpiredError>()( "ApprovalExpiredError", { executionId: Schema.String, }, { httpApiStatus: 410 },) { override get message(): string { return "This approval expired. Trigger the action again."; }} /** * Parse and bind one artifact-originated call, or fail with something the shell * can render inside the component that made it. * * The artifact is read through the request's own scoped executor, so an id that * isn't this caller's simply doesn't resolve. */const resolveArtifactCode = ( code: string, artifactId: string,): Effect.Effect<string, ArtifactActionError, ExecutorService> => Effect.gen(function* () { const executor = yield* ExecutorService; const resolution = yield* resolveArtifactAction({ code, artifactId, loadArtifact: (id) => executor.artifacts.get(id).pipe(Effect.catchCause(() => Effect.succeed(null))), }); if (resolution.status === "ok") return resolution.code; if (resolution.status === "binding_unresolved") { return yield* new ArtifactActionError({ error: "binding_unresolved", reason: resolution.message, role: resolution.role, integration: resolution.integration, }); } return yield* new ArtifactActionError({ error: resolution.status, reason: resolution.status === "artifact_unavailable" ? "This action refers to an artifact that isn't available on this account." : TOOL_CALL_CONTRACT_MESSAGE, }); }); /** * Record a paused artifact call so a later request can honour the approval. * * Best-effort by design: the in-memory pause is still live and still the fast * path, so a storage hiccup here must not turn a working approval into a failed * execution. It degrades to exactly the behaviour we had before this record * existed. */const recordPendingApproval = (approval: { readonly executionId: string; readonly artifactId: string; readonly code: string; readonly address: string;}): Effect.Effect<void, never, ExecutorService> => Effect.gen(function* () { const executor = yield* ExecutorService; yield* executor.pendingApprovals .put({ ...approval, expiresAt: Date.now() + PENDING_APPROVAL_TTL_MS }) .pipe(Effect.catchCause(() => Effect.void)); }); /** * Honour an approval whose paused fiber is no longer reachable. * * The record is read through the caller's OWN scoped executor, so an execution * id that is not this caller's reads as absent — the same-query ownership rule * the artifact path already uses. It is consumed on read, so one approval * authorizes exactly one invocation. * * A non-accept answer discards the record and reports the decline, which is the * same observable outcome as declining a live pause: nothing runs. */const resumeFromPendingApproval = (executionId: string, action: "accept" | "decline" | "cancel") => Effect.gen(function* () { const executor = yield* ExecutorService; const engine = yield* ExecutionEngineService; const approval = yield* executor.pendingApprovals .consume(executionId) .pipe(Effect.catchCause(() => Effect.succeed(null))); if (!approval) return null; if (action !== "accept") { return { status: "completed" as const, text: `Approval ${action === "decline" ? "declined" : "cancelled"}. Nothing ran.`, structured: { status: "declined", executionId, address: approval.address }, isError: false, }; } // The human approved, so run the recorded call with the gate satisfied. // `code` is the address the server itself resolved at pause time, so this // runs exactly the call that was described in the approval prompt. const outcome = yield* captureEngineError( engine.executeWithPause(approval.code, { autoApprove: true }), ); if (outcome.status === "completed") { const formatted = formatExecuteResult(outcome.result); return { status: "completed" as const, text: formatted.text, structured: formatted.structured, isError: formatted.isError, }; } // `autoApprove` accepts every gate inline, so a second pause is not // reachable here; surface it rather than silently dropping the call. const formatted = formatPausedExecution(outcome.execution); return { status: "paused" as const, text: formatted.text, structured: formatted.structured, }; }); export const ExecutionsHandlers = HttpApiBuilder.group(ExecutorApi, "executions", (handlers) => handlers .handle("getPaused", ({ params: path }) => capture( Effect.gen(function* () { const engine = yield* ExecutionEngineService; const paused = yield* captureEngineError(engine.getPausedExecution(path.executionId)); if (!paused) { return yield* new ExecutionNotFoundError({ executionId: path.executionId }); } return formatPausedExecution(paused); }), ), ) .handle("execute", ({ payload }) => capture( Effect.gen(function* () { const engine = yield* ExecutionEngineService; // An artifact-originated request is not arbitrary code. It is parsed // against the shell proxy's one grammar and rewritten through the // artifact's connection bindings, exactly as `execute-action` does in // the MCP host — the console's artifact page must not be a wider door // onto the same iframe. const code = payload.artifactId === undefined ? payload.code : yield* resolveArtifactCode(payload.code, payload.artifactId); const outcome = yield* captureEngineError( engine.executeWithPause(code, { autoApprove: payload.autoApprove }), ); if (outcome.status === "completed") { const formatted = formatExecuteResult(outcome.result); return { status: "completed" as const, text: formatted.text, structured: formatted.structured, isError: formatted.isError, }; } // The pause is a live fiber in THIS engine, which on a host that // builds an engine per request is gone the moment this response is // written. Record the resolved call so the approval can be honoured by // whichever instance serves the resume. Artifact calls only: a general // codemode pause can sit anywhere inside arbitrary code and is not // reconstructible from one address. if (payload.artifactId !== undefined) { yield* recordPendingApproval({ executionId: outcome.execution.id, artifactId: payload.artifactId, code, address: String(outcome.execution.elicitationContext.address), }); } const formatted = formatPausedExecution(outcome.execution); return { status: "paused" as const, text: formatted.text, structured: formatted.structured, }; }), ), ) .handle("resume", ({ params: path, payload }) => capture( Effect.gen(function* () { const engine = yield* ExecutionEngineService; const result = yield* captureEngineError( engine.resume(path.executionId, { action: payload.action, content: payload.content as Record<string, unknown> | undefined, ...(payload.persist === undefined ? {} : { meta: { persist: payload.persist } }), }), ); // No live pause: either this resume landed on a different engine // instance than the pause did (the normal case on a host that builds // an engine per request), or the window really has closed. if (!result) { const honoured = yield* resumeFromPendingApproval(path.executionId, payload.action); if (honoured) return honoured; return yield* new ApprovalExpiredError({ executionId: path.executionId }); } if (result.status === "completed") { const formatted = formatExecuteResult(result.result); return { status: "completed" as const, text: formatted.text, structured: formatted.structured, isError: formatted.isError, }; } const formatted = formatPausedExecution(result.execution); return { status: "paused" as const, text: formatted.text, structured: formatted.structured, }; }), ), ),);
import { HttpApiBuilder } from "effect/unstable/httpapi";
import { Effect } from "effect";
import { Schema } from "effect";
import { ExecutorApi } from "../api";
import { formatExecuteResult, formatPausedExecution } from "@executor-js/execution";
import { resolveArtifactAction } from "@executor-js/host-mcp/artifact-action";
import { TOOL_CALL_CONTRACT_MESSAGE } from "@executor-js/host-mcp/tool-call-code";
import { PENDING_APPROVAL_TTL_MS } from "@executor-js/sdk";
import { ExecutionEngineService, ExecutorService } from "../services";
import { capture, captureEngineError } from "@executor-js/api";
class ExecutionNotFoundError extends Schema.TaggedErrorClass<ExecutionNotFoundError>()(
"ExecutionNotFoundError",
{
executionId: Schema.String,
},
) {}
/**
* An artifact-originated execution that could not be resolved into a call.
*
* Carries the same vocabulary the MCP host's `execute-action` returns
* (`invalid_action_code`, `artifact_unavailable`, `binding_unresolved`) so the
* shell sees one contract whichever transport it reached the server through,
* and the binding UI that ships with sharing can key off `role`/`integration`.
*/
class ArtifactActionError extends Schema.TaggedErrorClass<ArtifactActionError>()(
"ArtifactActionError",
{
error: Schema.Literals(["invalid_action_code", "artifact_unavailable", "binding_unresolved"]),
reason: Schema.String,
role: Schema.optional(Schema.String),
integration: Schema.optional(Schema.String),
},
{ httpApiStatus: 400 },
) {
override get message(): string {
return this.reason;
}
}
/**
* An approval that expired before the human answered.
*
* Distinct from `ExecutionNotFoundError` (an id that was never ours) so the
* shell can tell the user their approval window closed and the action can simply
* be triggered again — nothing ran. 410 Gone, because the resource existed and
* deliberately no longer does.
*/
class ApprovalExpiredError extends Schema.TaggedErrorClass<ApprovalExpiredError>()(
"ApprovalExpiredError",
{
executionId: Schema.String,
},
{ httpApiStatus: 410 },
) {
override get message(): string {
return "This approval expired. Trigger the action again.";
}
}
/**
* Parse and bind one artifact-originated call, or fail with something the shell
* can render inside the component that made it.
*
* The artifact is read through the request's own scoped executor, so an id that
* isn't this caller's simply doesn't resolve.
*/
const resolveArtifactCode = (
code: string,
artifactId: string,
): Effect.Effect<string, ArtifactActionError, ExecutorService> =>
Effect.gen(function* () {
const executor = yield* ExecutorService;
const resolution = yield* resolveArtifactAction({
code,
artifactId,
loadArtifact: (id) =>
executor.artifacts.get(id).pipe(Effect.catchCause(() => Effect.succeed(null))),
});
if (resolution.status === "ok") return resolution.code;
if (resolution.status === "binding_unresolved") {
return yield* new ArtifactActionError({
error: "binding_unresolved",
reason: resolution.message,
role: resolution.role,
integration: resolution.integration,
});
}
return yield* new ArtifactActionError({
error: resolution.status,
reason:
resolution.status === "artifact_unavailable"
? "This action refers to an artifact that isn't available on this account."
: TOOL_CALL_CONTRACT_MESSAGE,
});
});
/**
* Record a paused artifact call so a later request can honour the approval.
*
* Best-effort by design: the in-memory pause is still live and still the fast
* path, so a storage hiccup here must not turn a working approval into a failed
* execution. It degrades to exactly the behaviour we had before this record
* existed.
*/
const recordPendingApproval = (approval: {
readonly executionId: string;
readonly artifactId: string;
readonly code: string;
readonly address: string;
}): Effect.Effect<void, never, ExecutorService> =>
Effect.gen(function* () {
const executor = yield* ExecutorService;
yield* executor.pendingApprovals
.put({ ...approval, expiresAt: Date.now() + PENDING_APPROVAL_TTL_MS })
.pipe(Effect.catchCause(() => Effect.void));
});
/**
* Honour an approval whose paused fiber is no longer reachable.
*
* The record is read through the caller's OWN scoped executor, so an execution
* id that is not this caller's reads as absent — the same-query ownership rule
* the artifact path already uses. It is consumed on read, so one approval
* authorizes exactly one invocation.
*
* A non-accept answer discards the record and reports the decline, which is the
* same observable outcome as declining a live pause: nothing runs.
*/
const resumeFromPendingApproval = (executionId: string, action: "accept" | "decline" | "cancel") =>
Effect.gen(function* () {
const executor = yield* ExecutorService;
const engine = yield* ExecutionEngineService;
const approval = yield* executor.pendingApprovals
.consume(executionId)
.pipe(Effect.catchCause(() => Effect.succeed(null)));
if (!approval) return null;
if (action !== "accept") {
return {
status: "completed" as const,
text: `Approval ${action === "decline" ? "declined" : "cancelled"}. Nothing ran.`,
structured: { status: "declined", executionId, address: approval.address },
isError: false,
};
}
// The human approved, so run the recorded call with the gate satisfied.
// `code` is the address the server itself resolved at pause time, so this
// runs exactly the call that was described in the approval prompt.
const outcome = yield* captureEngineError(
engine.executeWithPause(approval.code, { autoApprove: true }),
);
if (outcome.status === "completed") {
const formatted = formatExecuteResult(outcome.result);
return {
status: "completed" as const,
text: formatted.text,
structured: formatted.structured,
isError: formatted.isError,
};
}
// `autoApprove` accepts every gate inline, so a second pause is not
// reachable here; surface it rather than silently dropping the call.
const formatted = formatPausedExecution(outcome.execution);
return {
status: "paused" as const,
text: formatted.text,
structured: formatted.structured,
};
});
export const ExecutionsHandlers = HttpApiBuilder.group(ExecutorApi, "executions", (handlers) =>
handlers
.handle("getPaused", ({ params: path }) =>
capture(
Effect.gen(function* () {
const engine = yield* ExecutionEngineService;
const paused = yield* captureEngineError(engine.getPausedExecution(path.executionId));
if (!paused) {
return yield* new ExecutionNotFoundError({ executionId: path.executionId });
}
return formatPausedExecution(paused);
}),
),
)
.handle("execute", ({ payload }) =>
capture(
Effect.gen(function* () {
const engine = yield* ExecutionEngineService;
// An artifact-originated request is not arbitrary code. It is parsed
// against the shell proxy's one grammar and rewritten through the
// artifact's connection bindings, exactly as `execute-action` does in
// the MCP host — the console's artifact page must not be a wider door
// onto the same iframe.
const code =
payload.artifactId === undefined
? payload.code
: yield* resolveArtifactCode(payload.code, payload.artifactId);
const outcome = yield* captureEngineError(
engine.executeWithPause(code, { autoApprove: payload.autoApprove }),
);
if (outcome.status === "completed") {
const formatted = formatExecuteResult(outcome.result);
return {
status: "completed" as const,
text: formatted.text,
structured: formatted.structured,
isError: formatted.isError,
};
}
// The pause is a live fiber in THIS engine, which on a host that
// builds an engine per request is gone the moment this response is
// written. Record the resolved call so the approval can be honoured by
// whichever instance serves the resume. Artifact calls only: a general
// codemode pause can sit anywhere inside arbitrary code and is not
// reconstructible from one address.
if (payload.artifactId !== undefined) {
yield* recordPendingApproval({
executionId: outcome.execution.id,
artifactId: payload.artifactId,
code,
address: String(outcome.execution.elicitationContext.address),
});
}
const formatted = formatPausedExecution(outcome.execution);
return {
status: "paused" as const,
text: formatted.text,
structured: formatted.structured,
};
}),
),
)
.handle("resume", ({ params: path, payload }) =>
capture(
Effect.gen(function* () {
const engine = yield* ExecutionEngineService;
const result = yield* captureEngineError(
engine.resume(path.executionId, {
action: payload.action,
content: payload.content as Record<string, unknown> | undefined,
...(payload.persist === undefined ? {} : { meta: { persist: payload.persist } }),
}),
);
// No live pause: either this resume landed on a different engine
// instance than the pause did (the normal case on a host that builds
// an engine per request), or the window really has closed.
if (!result) {
const honoured = yield* resumeFromPendingApproval(path.executionId, payload.action);
if (honoured) return honoured;
return yield* new ApprovalExpiredError({ executionId: path.executionId });
}
if (result.status === "completed") {
const formatted = formatExecuteResult(result.result);
return {
status: "completed" as const,
text: formatted.text,
structured: formatted.structured,
isError: formatted.isError,
};
}
const formatted = formatPausedExecution(result.execution);
return {
status: "paused" as const,
text: formatted.text,
structured: formatted.structured,
};
}),
),
),
);