@agoric/swingset-vat 0.32.3-dev-0b910ea.0.0b910ea → 0.32.3-dev-df9efa9.0.df9efa9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,32 +2,28 @@
2
2
 
3
3
  [![License][license-image]][license-url]
4
4
 
5
- This repository contains another proof-of-concept Vat host, like
6
- PlaygroundVat. This one is modeled after KeyKOS "Domains": all Vats run on
7
- top of a "kernel" as if they were userspace processes in an operating system.
5
+ This repository implements an architecture in which Vats run on top of a
6
+ "kernel" as if they were userspace processes in an operating system.
8
7
  Each Vat gets access to a "syscall" object, through which it can send
9
8
  messages into the kernel. Vats receive message from the kernel via a
10
9
  "dispatch" function which they register at startup.
11
10
 
12
- Our goal is to experiment with different serialization/queueing mechanisms.
13
- One such mechanism is implemented so far, named "live slots", but we know
14
- this is insufficient to provide persistence across restarts.
11
+ See [docs](./docs) for more information.
15
12
 
16
- More docs are in the works. For now, try:
13
+ [SwingSet Runner](../swingset-runner) can be used to explore a SwingSet
14
+ instance, as can the simple `vat` utility in this package.
15
+
16
+ ## `vat` CLI
17
17
 
18
18
  ```console
19
- $ npm install
20
- $ npm test
21
- $ bin/vat run demo/encouragementBot
19
+ $ yarn install
20
+ $ bin/vat run --config demo/encouragementBot/swingset.json
22
21
  ```
23
22
 
24
- This repository is still in early development: APIs and features are not
25
- expected to stabilize for a while.
26
-
27
- ## REPL Shell
23
+ ### REPL Shell
28
24
 
29
25
  ```console
30
- $ bin/vat shell demo/encouragementBot
26
+ $ bin/vat shell --config demo/encouragementBot/swingset.json
31
27
  vat>
32
28
  ```
33
29
 
@@ -36,13 +32,13 @@ arguments. All vats are loaded, and three additional commands are added to
36
32
  the environment:
37
33
 
38
34
  * `dump()`: display the kernel tables, including the run queue
39
- * `step()`: execute the next action on the run queue
40
- * `run()`: keep stepping until the run queue is empty
35
+ * `await step()`: execute the next action on the run queue
36
+ * `await run()`: keep stepping until the run queue is empty
41
37
 
42
- ## Vat Basedirs
38
+ ### Vat Basedirs
43
39
 
44
- The main argument to `bin/vat` is a "basedir", which contains sources for all
45
- the Vats that should be loaded into the container.
40
+ `vat` must be called with either a SwingSet configuration file (as above) or a
41
+ "basedir" argument containing sources for all the Vats that should be loaded.
46
42
 
47
43
  Every file named `vat-*.js` (e.g. `vat-foo.js` and `vat-bar-none.js`) will
48
44
  create a new Vat (with names like `foo` and `bar-none`). Each directory named
@@ -53,7 +49,7 @@ In addition, a file named `bootstrap.js` must be present. This will contain
53
49
  the source for the "bootstrap Vat", which behaves like a regular Vat except:
54
50
 
55
51
  * At startup, its `bootstrap` method will be invoked, as `bootstrap(argv, vats)`
56
- * The `argv` value will be an array of strings, from the command line. So
52
+ * The `argv` value will be an array of strings from the command line. So
57
53
  running `bin/vat BASEDIR -- x1 x2 x3` will set `argv = ['x1', 'x2', 'x3']`.
58
54
  * The `vats` value will be an object with keys named after the other Vats
59
55
  that were created, and values which are each a Presence for that Vat's root
@@ -69,18 +65,23 @@ anything else.
69
65
  ## Vat Sources
70
66
 
71
67
  Each Vat source file (like `vat-foo.js` or `vat-bar.js`) is treated as a
72
- starting point for the `rollup` tool, which converts the Vat's source tree
68
+ starting point for a bundling process that converts the Vat's source tree
73
69
  into a single string (so it can be evaluated in a SES realm). This starting
74
70
  point can use `import` to reference shared local files. No
75
71
  non-local imports are allowed yet.
76
72
 
77
- The source file is expected to contain a single default export function named
78
- `setup`. This low-level function is invoked with a `syscall` object, and is
79
- expected to return a `dispatch` object.
73
+ The source file is expected to export a `buildRootObject` function that returns
74
+ a Vat "root object" managed by [Liveslots](../swingset-liveslots) with which
75
+ other vats can interact. It is also possible to bypass the "Live Slots" layer
76
+ when the vat options include `enableSetup: true` and are associated with a
77
+ `managerType` that supports such bypass (currently limited to "local"), by
78
+ exporting a default function named "setup" to be invoked with a `syscall` object
79
+ and expected to return a `dispatch` object (see
80
+ [BaseVatOptions](./src/types-external.js)).
80
81
 
81
82
  The "Live Slots" layer provides a function to build `dispatch` out of
82
83
  `syscall`, as well as a way to register the root object. This requires a few
83
- lines of boilerplate in the `setup()` function.
84
+ lines of boilerplate in the `setup` function.
84
85
 
85
86
  ```js
86
87
  function buildRootObject(E) {
@@ -101,143 +102,79 @@ export default function setup(syscall, state, helpers) {
101
102
  }
102
103
  ```
103
104
 
104
- ## Exposed (pass-by-presence) Objects
105
-
106
- The Live Slots system enables delivery of messages to remote "Callable
107
- Objects" objects, as long as those objects are of a particular form. All
108
- Callable Objects must follow these rules:
109
-
110
- * all enumerable properties must be functions
111
- * all properties, and the object itself, must be `harden()`ed
112
-
113
- The system can pass-by-copy "Data Objects" with similar rules:
105
+ ### Exposed (pass-by-presence) Objects
114
106
 
115
- * all enumerable properties must be non-functions
116
- * the object's prototype must be Array or Object (or null)
117
- * all properties, and the object itself, must be `harden()`ed
107
+ The Live Slots system enables delivery of inbound messages to local
108
+ "[Remotable](https://docs.agoric.com/guides/js-programming/far#pass-styles-and-harden)"
109
+ objects.
118
110
 
119
- ## Root Objects
111
+ ### Root Objects
120
112
 
121
- The "Root Object" is a callable object returned by `buildRootObject()`. It
113
+ The "Root Object" is a Remotable object returned by `buildRootObject()`. It
122
114
  will be made available to the Bootstrap Vat.
123
115
 
124
- ## Sending Messages with Presences
116
+ ### Sending Messages with Presences
125
117
 
126
- When a Callable Object is sent to another Vat, it arrives as a Presence. This
127
- is a special (empty) object that represents the Callable Object, and can be
128
- used to send it messages. If you are running SwingSet under SES (the default),
129
- then the so-called "wavy dot syntax" ([proposed for future ECMAScript](https://github.com/Agoric/proposal-wavy-dot))
130
- can be used to invoke eventual send methods.
118
+ Each Remotable object in the data of inbound messages arrives locally as a
119
+ Presence. This is a special (empty) object that represents a non-local
120
+ Remotable (i.e., a Remotable hosted by a remote Vat), and can be used to send it
121
+ messages via the special `E()` wrapper.
131
122
 
132
123
  Suppose Vat "bob" defines a Root Object with a method named `bar`. The
133
- bootstrap receives this as `vats.bob`, and can send a message like this:
134
-
135
- ```js
136
- function bootstrap(argv, vats) {
137
- vats.bob~.bar('hello bob');
138
- }
139
- ```
140
-
141
- The `~.` operator (pronounced "wavy dot") has the same left-to-right precedence as
142
- the `.` "dot" operator, so that example is equivalent to
143
- `HandledPromise.applyMethod(vats.bob, 'bar', ['hello bob'])`.
144
-
145
- If you are not running under SES and your Javascript environment does not yet support "wavy dot syntax" (i.e. running `"abc"~.[2]` results in a syntax error, not a Promise), then the special `E()` wrapper can be used to get a proxy from which methods can be invoked, which looks like:
124
+ bootstrap vat receives this as `vats.bob`, and can send a message like so:
146
125
 
147
126
  ```js
148
127
  function bootstrap(argv, vats) {
149
- E(vats.bob).bar('hello bob');
128
+ void E(vats.bob).bar('hello bob');
150
129
  }
151
130
  ```
152
131
 
153
- ## Other uses for wavy dot syntax
154
-
155
- The main purpose of the wavy dot syntax (and E wrapper) is to provide an
156
- "eventual send" operator, in which the message is always delivered on some
157
- later turn of the event loop. This happens regardless of whether the target
158
- is local or in some other Vat:
159
-
160
- ```js
161
- const t1 = {
162
- foo() { console.log('foo called'); },
163
- };
164
- t1~.foo()
165
- console.log('wavy dot called');
166
- ```
167
-
168
- will print:
169
-
170
- ```
171
- wavy dot called
172
- foo called
173
- ```
174
-
175
- This is equivalent to:
176
-
177
- ```js
178
- HandledPromise.applyMethod(t1, 'foo', [])
179
- ```
180
-
181
- or
182
-
183
- ```js
184
- E(t1).foo()
185
- ```
186
-
187
- ## Return Values
132
+ ### Return Values
188
133
 
189
134
  Eventual-sends return a Promise for their eventual result:
190
135
 
191
136
  ```js
192
- const fooP = bob~.foo();
193
- fooP.then(resolution => console.log('foo said', resolution),
194
- rejection => console.log('foo errored with', rejection));
137
+ const fooP = E(bob).foo();
138
+ fooP.then(
139
+ fulfillment => console.log('foo said', fulfillment),
140
+ rejection => console.log('foo errored with', rejection),
141
+ );
195
142
  ```
196
143
 
197
- ## Sending Messages to Promises
144
+ ### Sending Messages to Promises
198
145
 
199
- Wavy dot syntax also accepts Promises, just like `Promise.resolve`. The
200
- method will be invoked (on some future turn) on whatever the Promise resolves
201
- to.
146
+ Eventual-sends can target a not-yet-fulfilled Promise, deferring invocation of
147
+ the method until the Promise fulfills to an object.
202
148
 
203
- If wavy dot syntax is used on a Promise which rejects, the method is not invoked, and
204
- the return promise's `rejection` function is called instead:
149
+ If the target Promise rejects, or fulfills to something other than a Remotable
150
+ or Presence which supports the method, the method is not invoked and the
151
+ eventual-send promise is rejected.
205
152
 
206
153
  ```js
207
154
  const badP = Promise.reject(Error());
208
- const p2 = badP~.foo();
155
+ const p2 = E(badP).foo();
209
156
  p2.then(undefined, rej => console.log('rejected', rej));
210
157
  // prints 'rejected'
211
158
  ```
212
159
 
213
- If the Promise resolves to something which does not support the method,
214
- the method delivery will reject with a `TypeError`.
160
+ ### Promise Pipelining
215
161
 
216
- ## Promise Pipelining
217
-
218
- In `fooP = bob~.foo()`, `fooP` represents the (eventual) return value of
219
- whatever `foo()` executes. If that return value is also a Callable Object, it
162
+ In `fooP = E(bob).foo()`, `fooP` represents the (eventual) return value of
163
+ whatever `foo()` executes. If that return value is also a Remotable, it
220
164
  is possible to queue messages to be delivered to that future target. The
221
- Promise returned by an eventual-send can be used by wavy dot syntax too, and
165
+ Promise returned by an eventual-send can be used as a target itself, and
222
166
  the method invoked will be turned into a queued message that won't be
223
167
  delivered until the first promise resolves:
224
168
 
225
169
  ```js
226
- const db = databaseServer~.openDB();
227
- const row = db~.select(criteria)
228
- const success = row~.modify(newValue);
170
+ const db = E(databaseServer).openDB();
171
+ const row = E(db).select(criteria)
172
+ const success = E(row).modify(newValue);
229
173
  success.then(res => console.log('row modified'));
230
174
  ```
231
175
 
232
176
  If you don't care about them, the intermediate values can be discarded:
233
177
 
234
- ```js
235
- databaseServer~.openDB()~.select(criteria)~.modify(newValue)
236
- .then(res => console.log('row modified'));
237
- ```
238
-
239
- This can be done outside of SES in legacy Javascript environments with the `E()` wrapper:
240
-
241
178
  ```js
242
179
  E(E(E(databaseServer).openDB()).select(criteria)).modify(newValue)
243
180
  .then(res => console.log('row modified'));
@@ -246,24 +183,26 @@ E(E(E(databaseServer).openDB()).select(criteria)).modify(newValue)
246
183
  This sequence could be expressed with plain `then()` clauses, but by chaining
247
184
  them together without `then`, the kernel has enough information to
248
185
  speculatively deliver the later messages to the Vat in charge of answering
249
- the earlier messages. This avoids unnecessary roundtrips, by sending the
250
- later messages during "downtime" while the target Vat thinks about the answer
251
- to the first one.
186
+ the earlier messages (_if the deciding Vat is configured with
187
+ `enablePipelining: true`, which is not currently the case for any
188
+ [Liveslots](../swingset-liveslots) Vats_). This avoids unnecessary roundtrips,
189
+ by sending the later messages during "downtime" while the target Vat thinks
190
+ about the answer to the first one.
252
191
 
253
192
  This drastic reduction in latency is significant when the Vats are far away
254
193
  from each other, and the inter-Vat communication delay is large. The SwingSet
255
194
  container does not yet provide complete facilities for off-host messaging,
256
195
  but once that is implemented, promise pipelining will make a big difference.
257
196
 
258
- ## Presence Identity Comparison
197
+ ### Presence Identity Comparison
259
198
 
260
199
  Presences preserve identity as they move from one Vat to another:
261
200
 
262
- * Sending the same Callable Object multiple times will deliver the same
263
- Presence on the receiving Vat
201
+ * Sending the same Remotable multiple times will deliver the same Presence on
202
+ the receiving Vat
264
203
  * Sending a Presence back to its "home Vat" will arrive as the original
265
- Callable Object
266
- * Sending a Callable Object to two different Vats will result in Presences
204
+ Remotable
205
+ * Sending a Remotable to two different Vats will result in Presences
267
206
  that cannot be compared directly, because those two Vats can only
268
207
  communicate with messages. But if those two Vats both send those Presences
269
208
  to a third Vat, they will arrive as the same Presence object
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/swingset-vat",
3
- "version": "0.32.3-dev-0b910ea.0.0b910ea",
3
+ "version": "0.32.3-dev-df9efa9.0.df9efa9",
4
4
  "description": "Vat/Container Launcher",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -22,7 +22,7 @@
22
22
  "lint:eslint": "yarn run -T eslint ."
23
23
  },
24
24
  "devDependencies": {
25
- "@agoric/xsnap": "0.14.3-dev-0b910ea.0.0b910ea",
25
+ "@agoric/xsnap": "0.14.3-dev-df9efa9.0.df9efa9",
26
26
  "@types/better-sqlite3": "^7.6.13",
27
27
  "@types/microtime": "^2.1.0",
28
28
  "@types/tmp": "^0.2.0",
@@ -30,15 +30,15 @@
30
30
  "ava": "^5.3.0"
31
31
  },
32
32
  "dependencies": {
33
- "@agoric/internal": "0.3.3-dev-0b910ea.0.0b910ea",
34
- "@agoric/kmarshal": "0.1.1-dev-0b910ea.0.0b910ea",
35
- "@agoric/store": "0.9.3-dev-0b910ea.0.0b910ea",
36
- "@agoric/swing-store": "0.9.2-dev-0b910ea.0.0b910ea",
37
- "@agoric/swingset-liveslots": "0.10.3-dev-0b910ea.0.0b910ea",
38
- "@agoric/swingset-xsnap-supervisor": "0.10.3-dev-0b910ea.0.0b910ea",
39
- "@agoric/time": "0.3.3-dev-0b910ea.0.0b910ea",
40
- "@agoric/vat-data": "0.5.3-dev-0b910ea.0.0b910ea",
41
- "@agoric/xsnap-lockdown": "0.14.1-dev-0b910ea.0.0b910ea",
33
+ "@agoric/internal": "0.3.3-dev-df9efa9.0.df9efa9",
34
+ "@agoric/kmarshal": "0.1.1-dev-df9efa9.0.df9efa9",
35
+ "@agoric/store": "0.9.3-dev-df9efa9.0.df9efa9",
36
+ "@agoric/swing-store": "0.9.2-dev-df9efa9.0.df9efa9",
37
+ "@agoric/swingset-liveslots": "0.10.3-dev-df9efa9.0.df9efa9",
38
+ "@agoric/swingset-xsnap-supervisor": "0.10.3-dev-df9efa9.0.df9efa9",
39
+ "@agoric/time": "0.3.3-dev-df9efa9.0.df9efa9",
40
+ "@agoric/vat-data": "0.5.3-dev-df9efa9.0.df9efa9",
41
+ "@agoric/xsnap-lockdown": "0.14.1-dev-df9efa9.0.df9efa9",
42
42
  "@endo/base64": "^1.0.12",
43
43
  "@endo/bundle-source": "^4.1.2",
44
44
  "@endo/captp": "^4.4.8",
@@ -105,5 +105,5 @@
105
105
  "typeCoverage": {
106
106
  "atLeast": 76.59
107
107
  },
108
- "gitHead": "0b910eab0eaa75ad6e59126116767a3a6d8f4854"
108
+ "gitHead": "df9efa9cc6831e761d94e3cc74dfb7a9f886cdb1"
109
109
  }
@@ -401,6 +401,10 @@ export async function makeSwingsetController(
401
401
 
402
402
  slogDuration,
403
403
 
404
+ dumpLog(idx) {
405
+ return deepCopyJsonable(kernel.dumpLog(idx));
406
+ },
407
+
404
408
  dump() {
405
409
  return deepCopyJsonable(kernel.dump());
406
410
  },
@@ -617,7 +621,7 @@ export async function makeSwingsetController(
617
621
  * the two stages; this can happen, for example, in some debugging cases.
618
622
  *
619
623
  * @param {SwingSetConfig} config
620
- * @param {string[]} argv
624
+ * @param {string[]} bootstrapArgs
621
625
  * @param {{
622
626
  * kernelStorage?: SwingStoreKernelStorage;
623
627
  * env?: Record<string, string>;
@@ -634,7 +638,7 @@ export async function makeSwingsetController(
634
638
  */
635
639
  export async function buildVatController(
636
640
  config,
637
- argv = [],
641
+ bootstrapArgs = [],
638
642
  runtimeOptions = {},
639
643
  deviceEndowments = {},
640
644
  ) {
@@ -670,7 +674,7 @@ export async function buildVatController(
670
674
  if (!swingsetIsInitialized(kernelStorage)) {
671
675
  bootstrapResult = await initializeSwingset(
672
676
  config,
673
- argv,
677
+ bootstrapArgs,
674
678
  kernelStorage,
675
679
  initializationOptions,
676
680
  runtimeOptions,
@@ -191,8 +191,8 @@ export default function buildKernel(
191
191
 
192
192
  // This is a low-level output-only string logger used by old unit tests to
193
193
  // see whether vats made progress or not. The array it appends to is
194
- // available as c.dump().log . New unit tests should instead use the
195
- // 'result' value returned by c.queueToKref()
194
+ // available from controller methods `dumpLog` and `dump`. New unit tests
195
+ // should instead use the 'result' value returned by c.queueToKref().
196
196
  function testLog(...args) {
197
197
  const rendered = args.map(arg =>
198
198
  typeof arg === 'string' ? arg : JSON.stringify(arg, abbreviateReplacer),
@@ -2310,14 +2310,18 @@ export default function buildKernel(
2310
2310
  };
2311
2311
  },
2312
2312
 
2313
+ // Note: Logs are not deterministic, since log() does not go through the
2314
+ // syscall interface (and we replay transcripts one vat at a time, so any
2315
+ // log() calls that were interleaved during their original execution will be
2316
+ // sorted by vat in the replay). They are therefore not kept in the
2317
+ // persistent state and remain ephemeral.
2318
+ dumpLog(idx = 0) {
2319
+ return ephemeral.log.slice(idx);
2320
+ },
2313
2321
  dump() {
2314
- // note: dump().log is not deterministic, since log() does not go
2315
- // through the syscall interface (and we replay transcripts one vat at
2316
- // a time, so any log() calls that were interleaved during their
2317
- // original execution will be sorted by vat in the replay). Logs are
2318
- // not kept in the persistent state, only in ephemeral state.
2319
2322
  return { log: ephemeral.log, ...kernelKeeper.dump() };
2320
2323
  },
2324
+
2321
2325
  kdebugEnable,
2322
2326
 
2323
2327
  addImport,
package/tools/vat.js CHANGED
@@ -1,43 +1,98 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import { dirname } from 'node:path';
4
+ import yargsParser from 'yargs-parser';
3
5
  import '@endo/init/pre-bundle-source.js';
4
6
  import '@endo/init';
5
7
  import process from 'process';
6
8
  import repl from 'repl';
7
9
  import util from 'util';
8
- import { loadBasedir, buildVatController } from '../src/index.js';
10
+ import {
11
+ loadSwingsetConfigFile,
12
+ loadBasedir,
13
+ buildVatController,
14
+ } from '../src/index.js';
9
15
  import { buildLoopbox } from '../src/devices/loopbox/loopbox.js';
10
16
 
17
+ const USAGE = `
18
+ Usage: ${process.argv[1]} \\
19
+ {run|shell} [{-c|--config} <config file>] [<base dir>] [--] [<bootstrap arg>]...
20
+ `.trim();
21
+
22
+ const showUsage = (message, exitCode = 64) => {
23
+ const log = message ? console.error : console.log;
24
+ if (message) log(`${message}\n`);
25
+ log(USAGE);
26
+ return exitCode;
27
+ };
28
+
11
29
  function deepLog(item) {
12
30
  console.log(util.inspect(item, false, null, true));
13
31
  }
14
32
 
15
33
  async function main() {
16
- const argv = process.argv.splice(2);
17
- let withSES = true;
18
- if (argv[0] === '--no-ses') {
19
- withSES = false;
20
- argv.shift();
21
- }
34
+ const {
35
+ _: argv,
36
+ '--': extraArgs = [],
37
+ ...options
38
+ } = yargsParser(process.argv.slice(2), {
39
+ configuration: { 'populate--': true, 'unknown-options-as-args': true },
40
+ string: ['config'],
41
+ alias: { config: ['c'] },
42
+ });
22
43
  const command = argv.shift();
23
- if (command !== 'run' && command !== 'shell') {
24
- throw Error(`use 'vat run' or 'vat shell', not 'vat ${command}'`);
44
+ if (command === undefined) {
45
+ if (argv.includes('--help')) return showUsage();
46
+ return showUsage('Missing command');
47
+ } else if (command !== 'run' && command !== 'shell') {
48
+ return showUsage(
49
+ command === 'help' ? undefined : `Unrecognized command: ${command}`,
50
+ );
51
+ }
52
+ const configPath = options.config;
53
+ let basedir = /** @type {string | undefined} */ (argv.shift());
54
+ if (basedir === undefined) {
55
+ basedir = configPath ? dirname(configPath) : '.';
25
56
  }
26
- const basedir =
27
- argv[0] === '--' || argv[0] === undefined ? '.' : argv.shift();
28
- const vatArgv = argv[0] === '--' ? argv.slice(1) : argv;
57
+ const vatArgv = /** @type {string[]} */ ([...argv, ...extraArgs]);
29
58
 
30
- assert(basedir);
31
- const config = await loadBasedir(basedir);
32
- const { loopboxSrcPath, loopboxEndowments } = buildLoopbox('immediate');
33
- config.devices = [['loopbox', loopboxSrcPath, loopboxEndowments]];
59
+ const config = await (options.config
60
+ ? loadSwingsetConfigFile(options.config)
61
+ : loadBasedir(basedir));
62
+ assert(config);
63
+ config.devices ||= {};
64
+ const deviceEndowments = /** @type {Record<string, unknown>} */ ({});
65
+ // @ts-expect-error TS2339: Property 'loopboxSenders' does not exist on type 'SwingSetConfig'.
66
+ const { loopboxSenders } = config;
67
+ // @ts-expect-error
68
+ delete config.loopboxSenders;
69
+ if (loopboxSenders) {
70
+ const { loopboxSrcPath, loopboxEndowments } = buildLoopbox('immediate');
71
+ config.devices.loopbox = {
72
+ sourceSpec: loopboxSrcPath,
73
+ parameters: { senders: loopboxSenders },
74
+ };
75
+ deviceEndowments.loopbox = { ...loopboxEndowments };
76
+ }
34
77
 
35
- // @ts-expect-error expects string[], not boolean, in second position
36
- const controller = await buildVatController(config, withSES, vatArgv);
78
+ const controller = await buildVatController(
79
+ config,
80
+ vatArgv,
81
+ {},
82
+ deviceEndowments,
83
+ );
37
84
  if (command === 'run') {
38
85
  await controller.run();
86
+ for (const entry of controller.dumpLog()) console.log(entry);
39
87
  console.log('= vat finished');
40
88
  } else if (command === 'shell') {
89
+ let logIndex = 0;
90
+ const dumpLog = () => {
91
+ for (const entry of controller.dumpLog(logIndex)) {
92
+ logIndex += 1;
93
+ console.log(entry);
94
+ }
95
+ };
41
96
  const r = repl.start({ prompt: 'vat> ', replMode: repl.REPL_MODE_STRICT });
42
97
  r.context.dump = () => {
43
98
  const d = controller.dump();
@@ -51,15 +106,27 @@ async function main() {
51
106
  deepLog(d.acceptanceQueue);
52
107
  };
53
108
  r.context.dump2 = () => controller.dump();
54
- r.context.run = () => {
109
+ r.context.run = async () => {
55
110
  console.log('run!');
56
- return controller.run();
111
+ const result = await controller.run();
112
+ dumpLog();
113
+ return result;
57
114
  };
58
- r.context.step = () => {
115
+ r.context.step = async () => {
59
116
  console.log('step!');
60
- return controller.step();
117
+ const result = await controller.step();
118
+ dumpLog();
119
+ return result;
61
120
  };
62
121
  }
63
122
  }
64
123
 
65
- void main();
124
+ main().then(
125
+ exitCode => {
126
+ if (exitCode !== undefined) process.exitCode ||= exitCode;
127
+ },
128
+ err => {
129
+ console.error(err);
130
+ process.exitCode ||= 1;
131
+ },
132
+ );