Improve run system to batch-run notebooks

Previously notebooks were run a cell at a time, waiting for the response
before triggering the next execute. This drastically reduced the throughput
as each run was impacted by client-server lag. This commit alters
the architecture to batch-run notebooks as a whole.

Execution message ids are mapped to cells to ensure results are applied
to the correct place.

On the in-process kernel responses may come back before the message
is logged as sent, in this case responses are queued and applied at
the end of the entire run.