feat: add workflow console lifecycle explorer
This commit is contained in:
@@ -11,7 +11,21 @@ export {
|
||||
|
||||
export { normalizeLoopbackTarget } from "./target-policy.js";
|
||||
|
||||
export { WorkflowHealth, WorkflowSourcesList, WorkflowRpcs } from "./rpcs.js";
|
||||
export {
|
||||
WorkflowHealth,
|
||||
WorkflowSourcesList,
|
||||
WorkflowArtifactsList,
|
||||
WorkflowArtifactsInspect,
|
||||
WorkflowDeploymentsList,
|
||||
WorkflowDeploymentsInspect,
|
||||
WorkflowDeploymentsValidate,
|
||||
WorkflowRunsList,
|
||||
WorkflowRunsInspect,
|
||||
WorkflowRunsTrace,
|
||||
WorkflowRpcs,
|
||||
ArtifactRefSchema,
|
||||
TraceRangeSchema,
|
||||
} from "./rpcs.js";
|
||||
|
||||
export { WorkflowRpc, makeWorkflowRpcLayer } from "./service.js";
|
||||
export type { OperationExchange, WorkflowRpcError, OperationName } from "./service.js";
|
||||
|
||||
@@ -3,6 +3,21 @@ import {
|
||||
WorkflowHealthResultSchema,
|
||||
WorkflowSourcesListPayloadSchema,
|
||||
WorkflowSourcesListResultSchema,
|
||||
WorkflowArtifactsListPayloadSchema,
|
||||
WorkflowArtifactsListResultSchema,
|
||||
WorkflowArtifactsInspectPayloadSchema,
|
||||
WorkflowArtifactsInspectResultSchema,
|
||||
WorkflowDeploymentsListResultSchema,
|
||||
WorkflowDeploymentsInspectPayloadSchema,
|
||||
WorkflowDeploymentsInspectResultSchema,
|
||||
WorkflowDeploymentsValidatePayloadSchema,
|
||||
WorkflowDeploymentsValidateResultSchema,
|
||||
WorkflowRunsListPayloadSchema,
|
||||
WorkflowRunsListResultSchema,
|
||||
WorkflowRunsInspectPayloadSchema,
|
||||
WorkflowRunsInspectResultSchema,
|
||||
WorkflowRunsTracePayloadSchema,
|
||||
WorkflowRunsTraceResultSchema,
|
||||
} from "./rpcs.js";
|
||||
|
||||
export type OperationMeta = {
|
||||
@@ -87,6 +102,244 @@ const operationEntries: ReadonlyArray<OperationMeta> = [
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
method: "workflow.artifacts.list",
|
||||
label: "List artifacts",
|
||||
explanation: "List workflow artifacts with pagination",
|
||||
idempotency: "read",
|
||||
equivalentCli: (params) => {
|
||||
const p = Schema.decodeUnknownSync(WorkflowArtifactsListPayloadSchema)(
|
||||
params,
|
||||
{ onExcessProperty: "error" },
|
||||
);
|
||||
const parts = ["uv run wf artifact list"];
|
||||
if (p.limit != null) parts.push(`--limit ${p.limit}`);
|
||||
return parts.join(" ");
|
||||
},
|
||||
interpret: (result) => {
|
||||
const decoded = Schema.decodeUnknownSync(
|
||||
WorkflowArtifactsListResultSchema,
|
||||
)(result);
|
||||
return {
|
||||
items: decoded.nodes.map((node) => ({
|
||||
key: `${node.artifact_id}@${node.version}`,
|
||||
artifactId: node.artifact_id,
|
||||
version: node.version,
|
||||
kind: node.kind,
|
||||
displayName: node.display_name,
|
||||
description: node.description,
|
||||
outcomes: node.outcomes,
|
||||
requiredSources: node.required_sources,
|
||||
diagnosticCount: node.diagnostics.length,
|
||||
})),
|
||||
nextCursor: decoded.next_cursor,
|
||||
total: decoded.total,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
method: "workflow.artifacts.inspect",
|
||||
label: "Inspect artifact",
|
||||
explanation: "Inspect a workflow artifact by id and version",
|
||||
idempotency: "read",
|
||||
equivalentCli: (params) => {
|
||||
const p = Schema.decodeUnknownSync(WorkflowArtifactsInspectPayloadSchema)(
|
||||
params,
|
||||
{ onExcessProperty: "error" },
|
||||
);
|
||||
return `uv run wf artifact inspect ${p.artifact_id} --version ${p.version}`;
|
||||
},
|
||||
interpret: (result) => {
|
||||
const decoded = Schema.decodeUnknownSync(
|
||||
WorkflowArtifactsInspectResultSchema,
|
||||
)(result);
|
||||
return {
|
||||
artifactId: decoded.id,
|
||||
version: decoded.version,
|
||||
title: decoded.title,
|
||||
kind: decoded.kind,
|
||||
description: decoded.description,
|
||||
outcomes: decoded.outcomes,
|
||||
plan: decoded.plan,
|
||||
requiredCapabilities: decoded.required_capabilities,
|
||||
workflowDependencies: decoded.workflow_dependencies,
|
||||
createdFromCatalogVersion: decoded.created_from_catalog_version,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
method: "workflow.deployments.list",
|
||||
label: "List deployments",
|
||||
explanation: "List workflow deployments",
|
||||
idempotency: "read",
|
||||
equivalentCli: () => "uv run wf deploy list",
|
||||
interpret: (result) => {
|
||||
const decoded = Schema.decodeUnknownSync(
|
||||
WorkflowDeploymentsListResultSchema,
|
||||
)(result);
|
||||
return {
|
||||
items: decoded.deployments.map((d) => ({
|
||||
id: d.id,
|
||||
artifactId: d.artifact_id,
|
||||
artifactVersion: d.artifact_version,
|
||||
bindingCount: d.binding_count,
|
||||
driftPolicy: d.drift_policy,
|
||||
})),
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
method: "workflow.deployments.inspect",
|
||||
label: "Inspect deployment",
|
||||
explanation: "Inspect a workflow deployment by id",
|
||||
idempotency: "read",
|
||||
equivalentCli: (params) => {
|
||||
const p = Schema.decodeUnknownSync(
|
||||
WorkflowDeploymentsInspectPayloadSchema,
|
||||
)(params, { onExcessProperty: "error" });
|
||||
return `uv run wf deploy inspect ${p.deployment_id}`;
|
||||
},
|
||||
interpret: (result) => {
|
||||
const decoded = Schema.decodeUnknownSync(
|
||||
WorkflowDeploymentsInspectResultSchema,
|
||||
)(result);
|
||||
return {
|
||||
id: decoded.id,
|
||||
artifactId: decoded.artifact_id,
|
||||
artifactVersion: decoded.artifact_version,
|
||||
bindings: decoded.bindings.map((b) => ({
|
||||
logicalSource: b.logical_source,
|
||||
concreteSource: b.concrete_source,
|
||||
})),
|
||||
driftPolicy: decoded.drift_policy,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
method: "workflow.deployments.validate",
|
||||
label: "Validate deployment",
|
||||
explanation: "Validate a workflow deployment",
|
||||
idempotency: "read",
|
||||
equivalentCli: (params) => {
|
||||
const p = Schema.decodeUnknownSync(
|
||||
WorkflowDeploymentsValidatePayloadSchema,
|
||||
)(params, { onExcessProperty: "error" });
|
||||
return `uv run wf deploy validate ${p.deployment_id}`;
|
||||
},
|
||||
interpret: (result) => {
|
||||
const decoded = Schema.decodeUnknownSync(
|
||||
WorkflowDeploymentsValidateResultSchema,
|
||||
)(result);
|
||||
return {
|
||||
deploymentId: decoded.deployment_id,
|
||||
artifactId: decoded.artifact_id,
|
||||
artifactVersion: decoded.artifact_version,
|
||||
status: decoded.status,
|
||||
diagnostics: decoded.diagnostics,
|
||||
nextActions: decoded.next_actions,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
method: "workflow.runs.list",
|
||||
label: "List runs",
|
||||
explanation: "List workflow runs with pagination",
|
||||
idempotency: "read",
|
||||
equivalentCli: (params) => {
|
||||
const p = Schema.decodeUnknownSync(WorkflowRunsListPayloadSchema)(
|
||||
params,
|
||||
{ onExcessProperty: "error" },
|
||||
);
|
||||
const parts = ["uv run wf run list"];
|
||||
if (p.limit != null) parts.push(`--limit ${p.limit}`);
|
||||
return parts.join(" ");
|
||||
},
|
||||
interpret: (result) => {
|
||||
const decoded = Schema.decodeUnknownSync(WorkflowRunsListResultSchema)(
|
||||
result,
|
||||
);
|
||||
return {
|
||||
items: decoded.runs.map((run) => ({
|
||||
runId: run.run_id,
|
||||
deploymentId: run.deployment_id,
|
||||
artifactId: run.artifact_id,
|
||||
artifactVersion: run.artifact_version,
|
||||
status: run.status,
|
||||
resumeReadiness: run.resume_readiness,
|
||||
diagnosticCount: run.diagnostic_count,
|
||||
createdAt: run.created_at,
|
||||
updatedAt: run.updated_at,
|
||||
})),
|
||||
nextCursor: decoded.next_cursor,
|
||||
total: decoded.total,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
method: "workflow.runs.inspect",
|
||||
label: "Inspect run",
|
||||
explanation: "Inspect a workflow run by id",
|
||||
idempotency: "read",
|
||||
equivalentCli: (params) => {
|
||||
const p = Schema.decodeUnknownSync(WorkflowRunsInspectPayloadSchema)(
|
||||
params,
|
||||
{ onExcessProperty: "error" },
|
||||
);
|
||||
return `uv run wf run inspect ${p.run_id}`;
|
||||
},
|
||||
interpret: (result) => {
|
||||
const decoded = Schema.decodeUnknownSync(WorkflowRunsInspectResultSchema)(
|
||||
result,
|
||||
);
|
||||
return {
|
||||
runId: decoded.run_id,
|
||||
deploymentId: decoded.deployment_id,
|
||||
artifactId: decoded.artifact_id,
|
||||
artifactVersion: decoded.artifact_version,
|
||||
status: decoded.status,
|
||||
resumeReadiness: decoded.resume_readiness,
|
||||
interrupt: decoded.interrupt,
|
||||
outcome: decoded.outcome,
|
||||
error: decoded.error,
|
||||
output: decoded.output,
|
||||
diagnostics: decoded.diagnostics,
|
||||
traceCount: decoded.trace_count,
|
||||
nextActions: decoded.next_actions,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
method: "workflow.runs.trace",
|
||||
label: "Read run trace",
|
||||
explanation: "Read trace frames for a workflow run",
|
||||
idempotency: "read",
|
||||
equivalentCli: (params) => {
|
||||
const p = Schema.decodeUnknownSync(WorkflowRunsTracePayloadSchema)(
|
||||
params,
|
||||
{ onExcessProperty: "error" },
|
||||
);
|
||||
return `uv run wf run trace ${p.run_id} --from ${p.trace_range.start} --limit ${p.trace_range.limit}`;
|
||||
},
|
||||
interpret: (result) => {
|
||||
const r = result as Record<string, unknown>;
|
||||
const trace = (r.trace as ReadonlyArray<Record<string, unknown>> ?? []).map((entry) => ({
|
||||
nodeId: entry.node_id,
|
||||
stepType: entry.step_type,
|
||||
outcome: entry.outcome,
|
||||
resolvedInput: entry.resolved_input,
|
||||
output: entry.output,
|
||||
stateChanges: entry.state_changes,
|
||||
}));
|
||||
return {
|
||||
runId: r.run_id,
|
||||
status: r.status,
|
||||
trace,
|
||||
traceStart: r.trace_start,
|
||||
traceLimit: r.trace_limit,
|
||||
traceTruncated: r.trace_truncated,
|
||||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const registry: ReadonlyMap<string, OperationMeta> = new Map(
|
||||
|
||||
@@ -6,6 +6,26 @@ const NonNegativeIntegerSchema = Schema.Number.pipe(
|
||||
Schema.between(0, Number.MAX_SAFE_INTEGER),
|
||||
);
|
||||
|
||||
const PositiveIntegerSchema = Schema.Number.pipe(
|
||||
Schema.int(),
|
||||
Schema.between(1, Number.MAX_SAFE_INTEGER),
|
||||
);
|
||||
|
||||
const JsonObjectSchema = Schema.Record({
|
||||
key: Schema.String,
|
||||
value: Schema.Unknown,
|
||||
});
|
||||
|
||||
export const ArtifactRefSchema = Schema.Struct({
|
||||
artifact_id: Schema.String,
|
||||
version: PositiveIntegerSchema,
|
||||
});
|
||||
|
||||
export const TraceRangeSchema = Schema.Struct({
|
||||
start: NonNegativeIntegerSchema,
|
||||
limit: PositiveIntegerSchema,
|
||||
});
|
||||
|
||||
export const SourceSummarySchema = Schema.Struct({
|
||||
id: Schema.String,
|
||||
kind: Schema.String,
|
||||
@@ -48,4 +68,251 @@ export const WorkflowSourcesList = Rpc.make("workflow.sources.list", {
|
||||
error: Schema.Never,
|
||||
});
|
||||
|
||||
export const WorkflowRpcs = RpcGroup.make(WorkflowHealth, WorkflowSourcesList);
|
||||
// Artifacts
|
||||
export const WorkflowArtifactsListPayloadSchema = Schema.Struct({
|
||||
query: Schema.optional(Schema.String),
|
||||
kind: Schema.optional(Schema.Literal("workflow", "wrapper")),
|
||||
cursor: Schema.optional(Schema.String),
|
||||
limit: Schema.optional(PositiveIntegerSchema),
|
||||
});
|
||||
|
||||
const ArtifactNodeSchema = Schema.Struct({
|
||||
name: Schema.String,
|
||||
artifact_id: Schema.String,
|
||||
version: PositiveIntegerSchema,
|
||||
kind: Schema.String,
|
||||
display_name: Schema.String,
|
||||
description: Schema.NullOr(Schema.String),
|
||||
outcomes: Schema.Array(Schema.String),
|
||||
input_schema: JsonObjectSchema,
|
||||
output_schema: JsonObjectSchema,
|
||||
required_sources: Schema.Array(Schema.String),
|
||||
diagnostics: Schema.Array(Schema.Unknown),
|
||||
});
|
||||
|
||||
export const WorkflowArtifactsListResultSchema = Schema.Struct({
|
||||
nodes: Schema.Array(ArtifactNodeSchema),
|
||||
total: NonNegativeIntegerSchema,
|
||||
cursor: Schema.NullOr(Schema.String),
|
||||
next_cursor: Schema.NullOr(Schema.String),
|
||||
limit: Schema.optional(PositiveIntegerSchema),
|
||||
});
|
||||
|
||||
export const WorkflowArtifactsList = Rpc.make("workflow.artifacts.list", {
|
||||
payload: WorkflowArtifactsListPayloadSchema,
|
||||
success: WorkflowArtifactsListResultSchema,
|
||||
error: Schema.Never,
|
||||
});
|
||||
|
||||
export const WorkflowArtifactsInspectPayloadSchema = Schema.Struct({
|
||||
artifact_id: Schema.String,
|
||||
version: PositiveIntegerSchema,
|
||||
});
|
||||
|
||||
export const WorkflowArtifactsInspectResultSchema = Schema.Struct({
|
||||
id: Schema.String,
|
||||
version: PositiveIntegerSchema,
|
||||
title: Schema.String,
|
||||
kind: Schema.String,
|
||||
description: Schema.NullOr(Schema.String),
|
||||
outcomes: Schema.Array(Schema.String),
|
||||
input_schema: JsonObjectSchema,
|
||||
output_schema: JsonObjectSchema,
|
||||
plan: JsonObjectSchema,
|
||||
required_capabilities: Schema.Unknown,
|
||||
workflow_dependencies: Schema.Record({ key: Schema.String, value: Schema.Number }),
|
||||
created_from_catalog_version: Schema.NullOr(Schema.String),
|
||||
});
|
||||
|
||||
export const WorkflowArtifactsInspect = Rpc.make("workflow.artifacts.inspect", {
|
||||
payload: WorkflowArtifactsInspectPayloadSchema,
|
||||
success: WorkflowArtifactsInspectResultSchema,
|
||||
error: Schema.Never,
|
||||
});
|
||||
|
||||
// Deployments
|
||||
export const WorkflowDeploymentsListPayloadSchema = Schema.Struct({});
|
||||
|
||||
const DeploymentNodeSchema = Schema.Struct({
|
||||
id: Schema.String,
|
||||
artifact_id: Schema.String,
|
||||
artifact_version: PositiveIntegerSchema,
|
||||
binding_count: NonNegativeIntegerSchema,
|
||||
drift_policy: Schema.String,
|
||||
});
|
||||
|
||||
export const WorkflowDeploymentsListResultSchema = Schema.Struct({
|
||||
deployments: Schema.Array(DeploymentNodeSchema),
|
||||
});
|
||||
|
||||
export const WorkflowDeploymentsList = Rpc.make("workflow.deployments.list", {
|
||||
payload: WorkflowDeploymentsListPayloadSchema,
|
||||
success: WorkflowDeploymentsListResultSchema,
|
||||
error: Schema.Never,
|
||||
});
|
||||
|
||||
export const WorkflowDeploymentsInspectPayloadSchema = Schema.Struct({
|
||||
deployment_id: Schema.String,
|
||||
});
|
||||
|
||||
const DeploymentBindingSchema = Schema.Struct({
|
||||
logical_source: Schema.String,
|
||||
concrete_source: Schema.String,
|
||||
});
|
||||
|
||||
export const WorkflowDeploymentsInspectResultSchema = Schema.Struct({
|
||||
id: Schema.String,
|
||||
artifact_id: Schema.String,
|
||||
artifact_version: PositiveIntegerSchema,
|
||||
bindings: Schema.Array(DeploymentBindingSchema),
|
||||
drift_policy: Schema.String,
|
||||
});
|
||||
|
||||
export const WorkflowDeploymentsInspect = Rpc.make("workflow.deployments.inspect", {
|
||||
payload: WorkflowDeploymentsInspectPayloadSchema,
|
||||
success: WorkflowDeploymentsInspectResultSchema,
|
||||
error: Schema.Never,
|
||||
});
|
||||
|
||||
export const WorkflowDeploymentsValidatePayloadSchema = Schema.Struct({
|
||||
deployment_id: Schema.String,
|
||||
live_check: Schema.optional(Schema.Boolean),
|
||||
});
|
||||
|
||||
export const WorkflowDeploymentsValidateResultSchema = Schema.Struct({
|
||||
deployment_id: Schema.String,
|
||||
artifact_id: Schema.String,
|
||||
artifact_version: PositiveIntegerSchema,
|
||||
status: Schema.Literal("runnable", "unrunnable"),
|
||||
diagnostics: Schema.Array(Schema.Unknown),
|
||||
next_actions: Schema.Struct({
|
||||
can_continue: Schema.Boolean,
|
||||
can_save_now: Schema.NullOr(Schema.Boolean),
|
||||
recommended_next_tool: Schema.NullOr(Schema.String),
|
||||
reason: Schema.String,
|
||||
patch_examples: Schema.Array(Schema.Unknown),
|
||||
warnings: Schema.Array(Schema.String),
|
||||
}),
|
||||
});
|
||||
|
||||
export const WorkflowDeploymentsValidate = Rpc.make("workflow.deployments.validate", {
|
||||
payload: WorkflowDeploymentsValidatePayloadSchema,
|
||||
success: WorkflowDeploymentsValidateResultSchema,
|
||||
error: Schema.Never,
|
||||
});
|
||||
|
||||
// Runs
|
||||
export const WorkflowRunsListPayloadSchema = Schema.Struct({
|
||||
status: Schema.optional(Schema.Literal("completed", "failed", "interrupted")),
|
||||
cursor: Schema.optional(Schema.String),
|
||||
limit: Schema.optional(PositiveIntegerSchema),
|
||||
});
|
||||
|
||||
const RunNodeSchema = Schema.Struct({
|
||||
run_id: Schema.String,
|
||||
deployment_id: Schema.String,
|
||||
artifact_id: Schema.String,
|
||||
artifact_version: PositiveIntegerSchema,
|
||||
status: Schema.String,
|
||||
resume_readiness: Schema.String,
|
||||
diagnostic_count: NonNegativeIntegerSchema,
|
||||
created_at: Schema.String,
|
||||
updated_at: Schema.String,
|
||||
});
|
||||
|
||||
export const WorkflowRunsListResultSchema = Schema.Struct({
|
||||
runs: Schema.Array(RunNodeSchema),
|
||||
total: NonNegativeIntegerSchema,
|
||||
cursor: Schema.NullOr(Schema.String),
|
||||
next_cursor: Schema.NullOr(Schema.String),
|
||||
limit: PositiveIntegerSchema,
|
||||
});
|
||||
|
||||
export const WorkflowRunsList = Rpc.make("workflow.runs.list", {
|
||||
payload: WorkflowRunsListPayloadSchema,
|
||||
success: WorkflowRunsListResultSchema,
|
||||
error: Schema.Never,
|
||||
});
|
||||
|
||||
export const WorkflowRunsInspectPayloadSchema = Schema.Struct({
|
||||
run_id: Schema.String,
|
||||
});
|
||||
|
||||
const RunInterruptSchema = Schema.Struct({
|
||||
kind: Schema.String,
|
||||
payload: JsonObjectSchema,
|
||||
outcomes: Schema.Array(Schema.String),
|
||||
});
|
||||
|
||||
const RunNextActionsSchema = Schema.Struct({
|
||||
can_continue: Schema.Boolean,
|
||||
can_save_now: Schema.NullOr(Schema.Boolean),
|
||||
recommended_next_tool: Schema.NullOr(Schema.String),
|
||||
reason: Schema.String,
|
||||
patch_examples: Schema.Array(Schema.Unknown),
|
||||
warnings: Schema.Array(Schema.String),
|
||||
});
|
||||
|
||||
export const WorkflowRunsInspectResultSchema = Schema.Struct({
|
||||
run_id: Schema.String,
|
||||
deployment_id: Schema.String,
|
||||
artifact_id: Schema.String,
|
||||
artifact_version: PositiveIntegerSchema,
|
||||
status: Schema.String,
|
||||
resume_readiness: Schema.String,
|
||||
interrupt: Schema.NullOr(RunInterruptSchema),
|
||||
outcome: Schema.NullOr(Schema.String),
|
||||
error: Schema.NullOr(Schema.String),
|
||||
output: Schema.NullOr(JsonObjectSchema),
|
||||
diagnostics: Schema.Array(Schema.Unknown),
|
||||
trace_count: NonNegativeIntegerSchema,
|
||||
next_actions: RunNextActionsSchema,
|
||||
});
|
||||
|
||||
export const WorkflowRunsInspect = Rpc.make("workflow.runs.inspect", {
|
||||
payload: WorkflowRunsInspectPayloadSchema,
|
||||
success: WorkflowRunsInspectResultSchema,
|
||||
error: Schema.Never,
|
||||
});
|
||||
|
||||
export const WorkflowRunsTracePayloadSchema = Schema.Struct({
|
||||
run_id: Schema.String,
|
||||
trace_range: TraceRangeSchema,
|
||||
});
|
||||
|
||||
const TraceFrameSchema = Schema.Struct({
|
||||
node_id: Schema.String,
|
||||
step_type: Schema.String,
|
||||
resolved_input: JsonObjectSchema,
|
||||
outcome: Schema.String,
|
||||
output: JsonObjectSchema,
|
||||
state_changes: JsonObjectSchema,
|
||||
});
|
||||
|
||||
export const WorkflowRunsTraceResultSchema = Schema.Struct({
|
||||
run_id: Schema.String,
|
||||
status: Schema.String,
|
||||
trace: Schema.Array(TraceFrameSchema),
|
||||
trace_start: NonNegativeIntegerSchema,
|
||||
trace_limit: PositiveIntegerSchema,
|
||||
trace_truncated: Schema.Boolean,
|
||||
});
|
||||
|
||||
export const WorkflowRunsTrace = Rpc.make("workflow.runs.trace", {
|
||||
payload: WorkflowRunsTracePayloadSchema,
|
||||
success: WorkflowRunsTraceResultSchema,
|
||||
error: Schema.Never,
|
||||
});
|
||||
|
||||
export const WorkflowRpcs = RpcGroup.make(
|
||||
WorkflowHealth,
|
||||
WorkflowSourcesList,
|
||||
WorkflowArtifactsList,
|
||||
WorkflowArtifactsInspect,
|
||||
WorkflowDeploymentsList,
|
||||
WorkflowDeploymentsInspect,
|
||||
WorkflowDeploymentsValidate,
|
||||
WorkflowRunsList,
|
||||
WorkflowRunsInspect,
|
||||
WorkflowRunsTrace,
|
||||
);
|
||||
|
||||
@@ -73,6 +73,167 @@ const runEither = (
|
||||
.pipe(Effect.either);
|
||||
}).pipe(Effect.provide(makeWorkflowRpcLayer(options)), Effect.runPromise);
|
||||
|
||||
const lifecycleCases = [
|
||||
{
|
||||
operation: "workflow.artifacts.list" as const,
|
||||
params: { limit: 50 },
|
||||
result: {
|
||||
nodes: [
|
||||
{
|
||||
name: "workflow.report@1",
|
||||
artifact_id: "report",
|
||||
version: 1,
|
||||
kind: "workflow",
|
||||
display_name: "Report",
|
||||
description: null,
|
||||
outcomes: ["ok"],
|
||||
input_schema: { type: "object" },
|
||||
output_schema: { type: "object" },
|
||||
required_sources: ["local.report"],
|
||||
diagnostics: [],
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
cursor: null,
|
||||
next_cursor: null,
|
||||
limit: 50,
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "workflow.artifacts.inspect" as const,
|
||||
params: { artifact_id: "report", version: 1 },
|
||||
result: {
|
||||
id: "report",
|
||||
version: 1,
|
||||
title: "Report",
|
||||
kind: "workflow",
|
||||
description: null,
|
||||
outcomes: ["ok"],
|
||||
input_schema: { type: "object" },
|
||||
output_schema: { type: "object" },
|
||||
plan: { nodes: [], edges: [] },
|
||||
required_capabilities: [],
|
||||
workflow_dependencies: {},
|
||||
created_from_catalog_version: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "workflow.deployments.list" as const,
|
||||
params: {},
|
||||
result: {
|
||||
deployments: [
|
||||
{
|
||||
id: "report.default",
|
||||
artifact_id: "report",
|
||||
artifact_version: 1,
|
||||
binding_count: 1,
|
||||
drift_policy: "block",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "workflow.deployments.inspect" as const,
|
||||
params: { deployment_id: "report.default" },
|
||||
result: {
|
||||
id: "report.default",
|
||||
artifact_id: "report",
|
||||
artifact_version: 1,
|
||||
bindings: [{ logical_source: "local.report", concrete_source: "report" }],
|
||||
drift_policy: "block",
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "workflow.deployments.validate" as const,
|
||||
params: { deployment_id: "report.default" },
|
||||
result: {
|
||||
deployment_id: "report.default",
|
||||
artifact_id: "report",
|
||||
artifact_version: 1,
|
||||
status: "runnable",
|
||||
diagnostics: [],
|
||||
next_actions: {
|
||||
can_continue: true,
|
||||
can_save_now: null,
|
||||
recommended_next_tool: null,
|
||||
reason: "deployment is valid",
|
||||
patch_examples: [],
|
||||
warnings: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "workflow.runs.list" as const,
|
||||
params: { limit: 50 },
|
||||
result: {
|
||||
runs: [
|
||||
{
|
||||
run_id: "run_1",
|
||||
deployment_id: "report.default",
|
||||
artifact_id: "report",
|
||||
artifact_version: 1,
|
||||
status: "interrupted",
|
||||
resume_readiness: "ready",
|
||||
diagnostic_count: 0,
|
||||
created_at: "2026-07-02T00:00:00Z",
|
||||
updated_at: "2026-07-02T00:00:01Z",
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
cursor: null,
|
||||
next_cursor: null,
|
||||
limit: 50,
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "workflow.runs.inspect" as const,
|
||||
params: { run_id: "run_1" },
|
||||
result: {
|
||||
run_id: "run_1",
|
||||
deployment_id: "report.default",
|
||||
artifact_id: "report",
|
||||
artifact_version: 1,
|
||||
status: "interrupted",
|
||||
resume_readiness: "ready",
|
||||
interrupt: { kind: "review", payload: {}, outcomes: [] },
|
||||
outcome: null,
|
||||
error: null,
|
||||
output: null,
|
||||
diagnostics: [],
|
||||
trace_count: 0,
|
||||
next_actions: {
|
||||
can_continue: false,
|
||||
can_save_now: null,
|
||||
recommended_next_tool: null,
|
||||
reason: "run is interrupted",
|
||||
patch_examples: [],
|
||||
warnings: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "workflow.runs.trace" as const,
|
||||
params: { run_id: "run_1", trace_range: { start: 0, limit: 50 } },
|
||||
result: {
|
||||
run_id: "run_1",
|
||||
status: "interrupted",
|
||||
trace_start: 0,
|
||||
trace_limit: 50,
|
||||
trace_truncated: false,
|
||||
trace: [
|
||||
{
|
||||
node_id: "review",
|
||||
step_type: "interrupt",
|
||||
resolved_input: { report: "..." },
|
||||
outcome: "submitted",
|
||||
output: {},
|
||||
state_changes: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
] as const;
|
||||
|
||||
describe("WorkflowRpc", () => {
|
||||
it("uses @effect/rpc and returns exact raw request and response evidence", async () => {
|
||||
const fetch: typeof globalThis.fetch = async (input, init) => {
|
||||
@@ -251,3 +412,28 @@ describe("WorkflowRpc", () => {
|
||||
expect(result.left).toBeInstanceOf(RpcProtocolError);
|
||||
});
|
||||
});
|
||||
|
||||
describe("lifecycle operations", () => {
|
||||
for (const testCase of lifecycleCases) {
|
||||
it(`handles ${testCase.operation} successfully`, async () => {
|
||||
const fetch: typeof globalThis.fetch = async (input, init) => {
|
||||
const request = await requestBody(input, init);
|
||||
expect(request.method).toBe(testCase.operation);
|
||||
return jsonResponse({
|
||||
jsonrpc: "2.0",
|
||||
id: request.id,
|
||||
result: testCase.result,
|
||||
});
|
||||
};
|
||||
|
||||
const exchange = await runOperation(
|
||||
{ fetch },
|
||||
testCase.operation as "workflow.health" | "workflow.sources.list",
|
||||
testCase.params,
|
||||
);
|
||||
|
||||
expect(exchange.operation).toBe(testCase.operation);
|
||||
expect(exchange.interpreted).toBeDefined();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -27,13 +27,31 @@ import {
|
||||
WorkflowHealthPayloadSchema,
|
||||
WorkflowRpcs,
|
||||
WorkflowSourcesListPayloadSchema,
|
||||
WorkflowArtifactsListPayloadSchema,
|
||||
WorkflowArtifactsInspectPayloadSchema,
|
||||
WorkflowDeploymentsListPayloadSchema,
|
||||
WorkflowDeploymentsInspectPayloadSchema,
|
||||
WorkflowDeploymentsValidatePayloadSchema,
|
||||
WorkflowRunsListPayloadSchema,
|
||||
WorkflowRunsInspectPayloadSchema,
|
||||
WorkflowRunsTracePayloadSchema,
|
||||
} from "./rpcs.js";
|
||||
import { normalizeLoopbackTarget } from "./target-policy.js";
|
||||
|
||||
const DEFAULT_TIMEOUT_MILLISECONDS = 5_000;
|
||||
const DEFAULT_MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
|
||||
|
||||
export type OperationName = "workflow.health" | "workflow.sources.list";
|
||||
export type OperationName =
|
||||
| "workflow.health"
|
||||
| "workflow.sources.list"
|
||||
| "workflow.artifacts.list"
|
||||
| "workflow.artifacts.inspect"
|
||||
| "workflow.deployments.list"
|
||||
| "workflow.deployments.inspect"
|
||||
| "workflow.deployments.validate"
|
||||
| "workflow.runs.list"
|
||||
| "workflow.runs.inspect"
|
||||
| "workflow.runs.trace";
|
||||
|
||||
export interface WorkflowRpcOptions {
|
||||
readonly fetch?: typeof globalThis.fetch;
|
||||
@@ -62,7 +80,16 @@ export type WorkflowRpcError =
|
||||
| RpcDecodeError;
|
||||
|
||||
const isOperationName = (value: string): value is OperationName =>
|
||||
value === "workflow.health" || value === "workflow.sources.list";
|
||||
value === "workflow.health" ||
|
||||
value === "workflow.sources.list" ||
|
||||
value === "workflow.artifacts.list" ||
|
||||
value === "workflow.artifacts.inspect" ||
|
||||
value === "workflow.deployments.list" ||
|
||||
value === "workflow.deployments.inspect" ||
|
||||
value === "workflow.deployments.validate" ||
|
||||
value === "workflow.runs.list" ||
|
||||
value === "workflow.runs.inspect" ||
|
||||
value === "workflow.runs.trace";
|
||||
|
||||
const toExchange = (evidence: EvidenceRecord | null): RpcExchangeEvidence => ({
|
||||
request: evidence?.request.body ?? null,
|
||||
@@ -262,6 +289,62 @@ const executeImpl =
|
||||
);
|
||||
return yield* client.workflow["sources.list"](payload);
|
||||
}
|
||||
case "workflow.artifacts.list": {
|
||||
const payload = yield* decodeParams(
|
||||
WorkflowArtifactsListPayloadSchema,
|
||||
params,
|
||||
);
|
||||
return yield* client.workflow["artifacts.list"](payload);
|
||||
}
|
||||
case "workflow.artifacts.inspect": {
|
||||
const payload = yield* decodeParams(
|
||||
WorkflowArtifactsInspectPayloadSchema,
|
||||
params,
|
||||
);
|
||||
return yield* client.workflow["artifacts.inspect"](payload);
|
||||
}
|
||||
case "workflow.deployments.list": {
|
||||
const payload = yield* decodeParams(
|
||||
WorkflowDeploymentsListPayloadSchema,
|
||||
params,
|
||||
);
|
||||
return yield* client.workflow["deployments.list"](payload);
|
||||
}
|
||||
case "workflow.deployments.inspect": {
|
||||
const payload = yield* decodeParams(
|
||||
WorkflowDeploymentsInspectPayloadSchema,
|
||||
params,
|
||||
);
|
||||
return yield* client.workflow["deployments.inspect"](payload);
|
||||
}
|
||||
case "workflow.deployments.validate": {
|
||||
const payload = yield* decodeParams(
|
||||
WorkflowDeploymentsValidatePayloadSchema,
|
||||
params,
|
||||
);
|
||||
return yield* client.workflow["deployments.validate"](payload);
|
||||
}
|
||||
case "workflow.runs.list": {
|
||||
const payload = yield* decodeParams(
|
||||
WorkflowRunsListPayloadSchema,
|
||||
params,
|
||||
);
|
||||
return yield* client.workflow["runs.list"](payload);
|
||||
}
|
||||
case "workflow.runs.inspect": {
|
||||
const payload = yield* decodeParams(
|
||||
WorkflowRunsInspectPayloadSchema,
|
||||
params,
|
||||
);
|
||||
return yield* client.workflow["runs.inspect"](payload);
|
||||
}
|
||||
case "workflow.runs.trace": {
|
||||
const payload = yield* decodeParams(
|
||||
WorkflowRunsTracePayloadSchema,
|
||||
params,
|
||||
);
|
||||
return yield* client.workflow["runs.trace"](payload);
|
||||
}
|
||||
}
|
||||
}).pipe(
|
||||
Effect.provide(protocolLayer),
|
||||
|
||||
Reference in New Issue
Block a user