clavarium 0.2.0-next.8 → 0.2.0-next.9

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.
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "clavarium",
3
- "version": "0.2.0-next.8",
3
+ "version": "0.2.0-next.9",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "clavarium",
9
- "version": "0.2.0-next.8",
9
+ "version": "0.2.0-next.9",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@xterm/addon-fit": "^0.11.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clavarium",
3
- "version": "0.2.0-next.8",
3
+ "version": "0.2.0-next.9",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "A conversation is a VM with a terminal in it. Everything else is a folder.",
@@ -35,7 +35,8 @@
35
35
  "prepare": "git config core.hooksPath hooks 2>/dev/null || true",
36
36
  "check:hooks": "node tools/check-hooks.mjs",
37
37
  "test:e2e": "node --test --test-concurrency=1 \"e2e/*.e2e.mjs\"",
38
- "toolbox": "node deploy/parley-archive.mjs"
38
+ "toolbox": "node deploy/parley-archive.mjs",
39
+ "acceptance": "node acceptance/run.mjs"
39
40
  },
40
41
  "engines": {
41
42
  "node": ">=22"
@@ -38,6 +38,8 @@ export const STARTING_MS = 180_000;
38
38
  export const ANSWER_MS = 90_000;
39
39
  /* What the folders of a machine just started are waited for, at most, before its tool starts. */
40
40
  const MOUNTED_MS = 30_000;
41
+ /* How long a stop waits for folders being mounted (six folders took a minute on a real host). */
42
+ const CALM_MS = 180_000;
41
43
  const POLL_MS = 2000;
42
44
  /* The tool's screen not drawn this long after its terminal was opened: its
43
45
  start is said to be over all the same (the page then opens it itself). */
@@ -79,6 +81,13 @@ export class Conversations {
79
81
  when it was just built, restarted or its work moved; otherwise only
80
82
  looked at, and put back if they came loose (health.mjs). */
81
83
  mounted = async () => {};
84
+ /* Set by the server: what is being done to its machine's folders just
85
+ now (a look of health.mjs that may mount one again), or null. */
86
+ quiet = () => null;
87
+ /* Folders being mounted again after a start, by name: a stop waits for
88
+ them (#calm). Stopping a machine while the VM manager mounts a folder
89
+ in it was seen to hang the VM manager itself, for every machine. */
90
+ #mounting = new Map();
82
91
  /* Set by the server: told when its machine was restarted or built again
83
92
  by this server, in words for its chat, and whether the tool runs again
84
93
  (its chat says it, and the tool is told to read it again: talk.mjs). */
@@ -426,7 +435,10 @@ export class Conversations {
426
435
  const again = this.#mountAgain.delete(name);
427
436
  if (again || stopped) {
428
437
  this.#reach(name, 'folders'); tell();
429
- await Promise.race([Promise.resolve().then(() => this.mounted(name, again)).catch(() => {}), new Promise((r) => { setTimeout(r, MOUNTED_MS).unref?.(); })]);
438
+ const mounting = Promise.resolve().then(() => this.mounted(name, again)).catch(() => {});
439
+ this.#mounting.set(name, mounting);
440
+ void mounting.finally(() => { if (this.#mounting.get(name) === mounting) this.#mounting.delete(name); });
441
+ await Promise.race([mounting, new Promise((r) => { setTimeout(r, MOUNTED_MS).unref?.(); })]);
430
442
  }
431
443
  /* Woken from sleep: its tool is still there when its session is. */
432
444
  if (asleep) {
@@ -766,6 +778,7 @@ export class Conversations {
766
778
  async #cycle(name, c, between, restart) {
767
779
  const step = (text) => { if (this.#store.get(name)) this.#store.patch(name, { step: text }); };
768
780
  try {
781
+ await this.#calm(name, step);
769
782
  this.drop(name);
770
783
  let state = await this.#vms.state(name);
771
784
  if (state !== 'Stopped') {
@@ -778,7 +791,7 @@ export class Conversations {
778
791
  await this.#watched(name, 'stop', this.#vms.kill(name));
779
792
  state = await this.#vms.state(name);
780
793
  }
781
- if (state !== 'Stopped') return `the machine did not stop (it is ${state ?? 'gone'}).`;
794
+ if (state !== 'Stopped') return `the machine did not stop (${state ? `it is ${state}` : 'its state could not be read'}).`;
782
795
  }
783
796
  const failed = await between();
784
797
  if (!restart) return failed;
@@ -794,6 +807,18 @@ export class Conversations {
794
807
  } catch { return 'it stopped on an unexpected error.'; }
795
808
  }
796
809
 
810
+ /* Before its machine is stopped: its folders being mounted again are
811
+ left to finish, within a bound (a new look does not start meanwhile:
812
+ health.mjs leaves a machine that is not `ready` alone). */
813
+ async #calm(name, step) {
814
+ const pending = [this.#mounting.get(name), this.quiet(name)].filter(Boolean);
815
+ if (pending.length === 0) return;
816
+ step('Waiting for its folders to be mounted');
817
+ let timer;
818
+ await Promise.race([Promise.allSettled(pending), new Promise((r) => { timer = setTimeout(r, CALM_MS); timer.unref?.(); })]);
819
+ clearTimeout(timer);
820
+ }
821
+
797
822
  /* `chat`: the same, as its chat says it. */
798
823
  async #settle(name, said, chat, failed = false) {
799
824
  if (!this.#store.get(name)) return;
package/server/health.mjs CHANGED
@@ -120,6 +120,9 @@ export class Health {
120
120
  of(name) { return this.#records.get(name) ?? null; }
121
121
  forget(name) { this.#records.delete(name); }
122
122
 
123
+ /* The look under way (it may be mounting a folder again), or null. */
124
+ busy(name) { return this.#running.get(name) ?? null; }
125
+
123
126
  /* Looks now. One look at a time per conversation: a second caller gets the same. */
124
127
  check(name, { repair = true } = {}) {
125
128
  const going = this.#running.get(name);
package/server/main.mjs CHANGED
@@ -337,6 +337,7 @@ export async function startHome({ port, host, dataDir, mntDir = dataDir, gitRese
337
337
  /* A machine just started: its folders are mounted again (after a build,
338
338
  a restart, a move) or looked at and put back if they came loose (after
339
339
  a wake), before its tool starts in them. */
340
+ all.quiet = (name) => health.busy(name);
340
341
  all.mounted = async (name, again) => { if (again) await folders.remount(name).catch(() => {}); await health.check(name).catch(() => {}); };
341
342
  /* What the VM manager says each machine has is what is recorded (a record
342
343
  may say otherwise: set by hand, or by an earlier version). Not waited for. */