pinomad 0.10.0 → 0.12.0

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.
Files changed (98) hide show
  1. package/README.md +11 -9
  2. package/dist/chunk-41q0saz0.js +73 -0
  3. package/dist/chunk-42nqtx5r.js +22 -0
  4. package/dist/{chunk-wb10efyq.js → chunk-445cevpm.js} +1 -1
  5. package/dist/{chunk-jgd38rna.js → chunk-47ff1d1t.js} +206 -178
  6. package/dist/chunk-5qf1grkk.js +220 -0
  7. package/dist/{chunk-4h7pkkzr.js → chunk-7mn38n4f.js} +3 -3
  8. package/dist/{chunk-zehwx009.js → chunk-7wj0jh3t.js} +1 -1
  9. package/dist/{chunk-5791mb0b.js → chunk-agq4tn9r.js} +47 -23
  10. package/dist/{chunk-c5p9xkd8.js → chunk-aqj9wfmb.js} +3 -3
  11. package/dist/{chunk-cx8k63by.js → chunk-g3ahp7b9.js} +12 -1
  12. package/dist/chunk-gg2dcqbg.js +458 -0
  13. package/dist/{chunk-73efzaf1.js → chunk-jeqsdcj1.js} +30 -13
  14. package/dist/{chunk-czj9nfrz.js → chunk-ksfxqd8s.js} +500 -41
  15. package/dist/{chunk-p978qj9m.js → chunk-mnmqyq7d.js} +565 -39
  16. package/dist/{chunk-4sepcfpy.js → chunk-np4jd070.js} +155 -3
  17. package/dist/{chunk-v414b5pp.js → chunk-nppgxh6c.js} +2 -2
  18. package/dist/chunk-nq57fdnq.js +106 -0
  19. package/dist/{chunk-sygeb9qb.js → chunk-pqe7q1s7.js} +1 -1
  20. package/dist/{chunk-w2m9xkks.js → chunk-t5abqsva.js} +199 -72
  21. package/dist/{chunk-8gywfw5n.js → chunk-t91zt5f0.js} +2 -51
  22. package/dist/{chunk-vax4zspw.js → chunk-vqkb70d7.js} +3 -3
  23. package/dist/chunk-vx7dy07y.js +26 -0
  24. package/dist/pinomad.js +19 -12
  25. package/package.json +2 -1
  26. package/web/assets/astro-DOly5024.js +1 -0
  27. package/web/assets/c-BIGW1oBm.js +1 -0
  28. package/web/assets/core-DnpzcQzs.js +4 -0
  29. package/web/assets/cpp-DddhVWhK.js +1 -0
  30. package/web/assets/csharp-B_TYUkTI.js +1 -0
  31. package/web/assets/css-Z72CF80r.js +1 -0
  32. package/web/assets/dart-CtKP3W-j.js +1 -0
  33. package/web/assets/diff-D97Zzqfu.js +1 -0
  34. package/web/assets/dockerfile-BcOcwvcX.js +1 -0
  35. package/web/assets/elixir-DkJLSxtU.js +1 -0
  36. package/web/assets/engine-javascript-BHPwbUfN.js +141 -0
  37. package/web/assets/engine-oniguruma-CQjQPVHy.js +1 -0
  38. package/web/assets/fish-BvzEVeQv.js +1 -0
  39. package/web/assets/go-C27-OAKa.js +1 -0
  40. package/web/assets/graphql-ChdNCCLP.js +1 -0
  41. package/web/assets/haskell-Df6bDoY_.js +1 -0
  42. package/web/assets/hcl-BWvSN4gD.js +1 -0
  43. package/web/assets/highlighted-diff-Dn5ifsvK.js +175 -0
  44. package/web/assets/highlights-DnHv6NBM.js +4 -0
  45. package/web/assets/highlights-themes-CUdrlZk3.js +1 -0
  46. package/web/assets/html-BlS82ilI.js +1 -0
  47. package/web/assets/{index-DoyHMPwf.css → index-BV0ecolo.css} +1 -1
  48. package/web/assets/index-DNsTTgPE.js +396 -0
  49. package/web/assets/ini-BEwlwnbL.js +1 -0
  50. package/web/assets/java-CylS5w8V.js +1 -0
  51. package/web/assets/javascript-wDzz0qaB.js +1 -0
  52. package/web/assets/json-Cp-IABpG.js +1 -0
  53. package/web/assets/jsonc-Des-eS-w.js +1 -0
  54. package/web/assets/jsx-g9-lgVsj.js +1 -0
  55. package/web/assets/kotlin-BdnUsdx6.js +1 -0
  56. package/web/assets/less-B1dDrJ26.js +1 -0
  57. package/web/assets/lua-BaeVxFsk.js +1 -0
  58. package/web/assets/make-CHLpvVh8.js +1 -0
  59. package/web/assets/markdown-Cvjx9yec.js +1 -0
  60. package/web/assets/nix-CwoSXNpI.js +1 -0
  61. package/web/assets/objective-c-DXmwc3jG.js +1 -0
  62. package/web/assets/php-DoruYMF0.js +1 -0
  63. package/web/assets/pierre-dark-CyvmCCZW.js +1 -0
  64. package/web/assets/pierre-dark-VKo95ZgA.js +1 -0
  65. package/web/assets/pierre-light-480U9XYS.js +1 -0
  66. package/web/assets/pierre-light-BVSW-Xev.js +1 -0
  67. package/web/assets/postcss-DnzrXTVv.js +1 -0
  68. package/web/assets/powershell-Dd0kTxKP.js +1 -0
  69. package/web/assets/prisma-Vru482bI.js +1 -0
  70. package/web/assets/proto-C7zT0LnQ.js +1 -0
  71. package/web/assets/python-B6aJPvgy.js +1 -0
  72. package/web/assets/ruby-B43yHlf9.js +1 -0
  73. package/web/assets/rust-DPvrY3IQ.js +1 -0
  74. package/web/assets/scala-CqE71os6.js +1 -0
  75. package/web/assets/scss-Cw6XMfVs.js +1 -0
  76. package/web/assets/shellscript-Yzrsuije.js +1 -0
  77. package/web/assets/shiki-BuPP5HCg.js +2 -0
  78. package/web/assets/shiki-langs-FJk0v0Ga.js +2 -0
  79. package/web/assets/shiki-wasm-CgFRq91U.js +1 -0
  80. package/web/assets/sql-DkvoVjww.js +1 -0
  81. package/web/assets/stream-tokenizer-Bl6bisIa.js +4 -0
  82. package/web/assets/svelte-DoIXUgJc.js +1 -0
  83. package/web/assets/swift-C2oV4EkX.js +1 -0
  84. package/web/assets/terraform-BETggiCN.js +1 -0
  85. package/web/assets/theming-themes-BL_CC-Mm.js +1 -0
  86. package/web/assets/toml-vGWfd6FD.js +1 -0
  87. package/web/assets/tsx-COt5Ahok.js +1 -0
  88. package/web/assets/typescript-BPQ3VLAy.js +1 -0
  89. package/web/assets/vue-CwSeJimo.js +1 -0
  90. package/web/assets/xml-sdJ4AIDG.js +1 -0
  91. package/web/assets/xterm-view-6GBZ9nXN.css +32 -0
  92. package/web/assets/xterm-view-ZsuzJb2C.js +38 -0
  93. package/web/assets/yaml-Buea-lGh.js +1 -0
  94. package/web/assets/zig-VOosw3JB.js +1 -0
  95. package/web/index.html +2 -2
  96. package/dist/chunk-bhtx2c8a.js +0 -16
  97. package/dist/chunk-c05g401k.js +0 -170
  98. package/web/assets/index-BdBjP1qJ.js +0 -395
@@ -1,14 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- serveWebClient
4
- } from "./chunk-wb10efyq.js";
5
2
  import {
6
3
  Published,
7
4
  constantValue,
8
5
  openHostSettings,
9
6
  sameList,
10
7
  settingsDefaults
11
- } from "./chunk-v414b5pp.js";
8
+ } from "./chunk-nppgxh6c.js";
12
9
  import {
13
10
  DevicesDoc,
14
11
  RevocationsDoc,
@@ -16,21 +13,24 @@ import {
16
13
  ensureDevices,
17
14
  loadHostKey,
18
15
  loadRelayKey,
16
+ pairingOffers
17
+ } from "./chunk-t91zt5f0.js";
18
+ import {
19
+ REMOVAL_NOTICE_FILE,
20
+ markRemoved,
19
21
  normalizeOrigin,
20
- pairingOffers,
21
22
  readServerBinding,
22
23
  readServerConfig,
23
24
  resolveRelayOrigin
24
- } from "./chunk-8gywfw5n.js";
25
+ } from "./chunk-nq57fdnq.js";
26
+ import {
27
+ serveWebClient
28
+ } from "./chunk-445cevpm.js";
25
29
  import {
26
30
  relayHostId,
27
31
  relayWsBase,
28
32
  startRelayLink
29
- } from "./chunk-cx8k63by.js";
30
- import {
31
- packagedVersion,
32
- webRoot
33
- } from "./chunk-zehwx009.js";
33
+ } from "./chunk-g3ahp7b9.js";
34
34
  import {
35
35
  ATTACHMENT_TTL_MS,
36
36
  INVALID_FRAME_CLOSE_CODE,
@@ -56,7 +56,7 @@ import {
56
56
  rtcCandidateType,
57
57
  sdpCandidates,
58
58
  withRtcCandidates
59
- } from "./chunk-jgd38rna.js";
59
+ } from "./chunk-47ff1d1t.js";
60
60
  import {
61
61
  ENDPOINT_SET_SCHEMA_VERSION,
62
62
  MAX_HOST_ENDPOINT_URL_LENGTH,
@@ -72,16 +72,20 @@ import {
72
72
  parseEndpointSet,
73
73
  respondIK,
74
74
  toBase64Url
75
- } from "./chunk-sygeb9qb.js";
75
+ } from "./chunk-pqe7q1s7.js";
76
76
  import {
77
77
  DEFAULT_DATA_DIR
78
78
  } from "./chunk-ngf1zatm.js";
79
+ import {
80
+ packagedVersion,
81
+ webRoot
82
+ } from "./chunk-7wj0jh3t.js";
79
83
  import {
80
84
  __require
81
85
  } from "./chunk-df32avw6.js";
82
86
 
83
87
  // apps/host/src/main.ts
84
- import { randomUUID as randomUUID3 } from "node:crypto";
88
+ import { randomUUID as randomUUID4 } from "node:crypto";
85
89
  import { mkdir as mkdir6, readFile as readFile7, writeFile as writeFile5 } from "node:fs/promises";
86
90
  import { homedir as homedir3 } from "node:os";
87
91
  import { join as join12, resolve as resolve7 } from "node:path";
@@ -710,11 +714,7 @@ async function checkoutInfo(harness, input, context) {
710
714
  }
711
715
  };
712
716
  }
713
- async function cleanupArchivedWorktrees(harness, rootId, context) {
714
- const index = await harness.snapshot(IndexDoc, context);
715
- const checkouts = index?.checkouts ?? [];
716
- if (checkouts.length === 0)
717
- return [];
717
+ async function conversationSubtree(harness, rootId, context) {
718
718
  const all = [];
719
719
  let cursor;
720
720
  do {
@@ -741,6 +741,27 @@ async function cleanupArchivedWorktrees(harness, rootId, context) {
741
741
  }
742
742
  }
743
743
  }
744
+ return subtree;
745
+ }
746
+ async function familyRoot(harness, id, context) {
747
+ let current = id;
748
+ const seen = new Set;
749
+ while (!seen.has(current)) {
750
+ seen.add(current);
751
+ const record = await harness.commit((tx) => tx.conversation(current), context);
752
+ const parent = record?.parent?.conversationId ?? record?.owner?.conversationId;
753
+ if (parent === undefined)
754
+ return current;
755
+ current = parent;
756
+ }
757
+ return current;
758
+ }
759
+ async function cleanupArchivedWorktrees(harness, rootId, context) {
760
+ const index = await harness.snapshot(IndexDoc, context);
761
+ const checkouts = index?.checkouts ?? [];
762
+ if (checkouts.length === 0)
763
+ return [];
764
+ const subtree = await conversationSubtree(harness, rootId, context);
744
765
  const kept = [];
745
766
  for (const record of checkouts.filter((checkout) => subtree.has(checkout.conversationId))) {
746
767
  const reason = await removeWorktree(record);
@@ -1449,6 +1470,7 @@ var todo = {
1449
1470
  };
1450
1471
 
1451
1472
  // apps/host/src/gateway.ts
1473
+ import { stat as stat6 } from "node:fs/promises";
1452
1474
  import { createServer } from "node:http";
1453
1475
  import { homedir, networkInterfaces } from "node:os";
1454
1476
  import { join as join7, sep as sep5 } from "node:path";
@@ -2054,6 +2076,329 @@ function dataChannelCarrier(channel, timers, log, onClosed) {
2054
2076
  };
2055
2077
  }
2056
2078
 
2079
+ // apps/host/src/terminals.ts
2080
+ import { randomUUID as randomUUID2 } from "node:crypto";
2081
+ import { userInfo } from "node:os";
2082
+ var TERMINAL_LIMIT = 8;
2083
+ var BUFFER_SIZE = 512 * 1024;
2084
+ var FLUSH_MS = 8;
2085
+ var MAX_CHUNK = 16 * 1024;
2086
+ var MAX_TITLE = 1024;
2087
+ var TITLE = /\x1b\][02];([^\x07\x1b]*)(?:\x07|\x1b\\)/g;
2088
+
2089
+ class Terminals {
2090
+ #options;
2091
+ #limit;
2092
+ #terminals = new Map;
2093
+ #numbers = new Map;
2094
+ #watchers = new Map;
2095
+ constructor(options) {
2096
+ this.#options = options;
2097
+ this.#limit = options.limit ?? TERMINAL_LIMIT;
2098
+ }
2099
+ get limit() {
2100
+ return this.#limit;
2101
+ }
2102
+ list(owner) {
2103
+ const terminals = [...this.#terminals.values()].filter((terminal) => terminal.owner === owner).map(info);
2104
+ return { owner, limit: this.#limit, terminals };
2105
+ }
2106
+ watch(owner, listener) {
2107
+ let set = this.#watchers.get(owner);
2108
+ if (set === undefined)
2109
+ this.#watchers.set(owner, set = new Set);
2110
+ set.add(listener);
2111
+ listener(this.list(owner));
2112
+ return () => {
2113
+ set.delete(listener);
2114
+ if (set.size === 0)
2115
+ this.#watchers.delete(owner);
2116
+ };
2117
+ }
2118
+ open(owner, cwd, size) {
2119
+ const count = [...this.#terminals.values()].filter((terminal2) => terminal2.owner === owner).length;
2120
+ if (count >= this.#limit)
2121
+ throw new Error(`Terminal limit reached (${this.#limit})`);
2122
+ const number = (this.#numbers.get(owner) ?? 0) + 1;
2123
+ this.#numbers.set(owner, number);
2124
+ const terminal = {
2125
+ id: randomUUID2(),
2126
+ owner,
2127
+ cwd,
2128
+ number,
2129
+ pty: undefined,
2130
+ generation: 0,
2131
+ cols: size.cols,
2132
+ rows: size.rows,
2133
+ title: undefined,
2134
+ rename: undefined,
2135
+ exit: undefined,
2136
+ buffer: "",
2137
+ start: 0,
2138
+ pending: "",
2139
+ flushTimer: undefined,
2140
+ titleCarry: "",
2141
+ viewers: new Set
2142
+ };
2143
+ this.#terminals.set(terminal.id, terminal);
2144
+ try {
2145
+ this.#start(terminal);
2146
+ } catch (error) {
2147
+ this.#terminals.delete(terminal.id);
2148
+ throw error;
2149
+ }
2150
+ this.#changed(owner);
2151
+ return terminal.id;
2152
+ }
2153
+ input(id, data, size) {
2154
+ const terminal = this.#get(id);
2155
+ if (size !== undefined)
2156
+ this.#resize(terminal, size);
2157
+ terminal.pty?.write(data);
2158
+ }
2159
+ resize(id, size) {
2160
+ this.#resize(this.#get(id), size);
2161
+ }
2162
+ rename(id, name) {
2163
+ const terminal = this.#get(id);
2164
+ const trimmed = name?.trim();
2165
+ terminal.rename = trimmed === undefined || trimmed === "" ? undefined : trimmed;
2166
+ this.#changed(terminal.owner);
2167
+ }
2168
+ restart(id, size) {
2169
+ const terminal = this.#get(id);
2170
+ this.#stop(terminal);
2171
+ terminal.start += terminal.buffer.length + terminal.pending.length + 1;
2172
+ terminal.buffer = "";
2173
+ terminal.pending = "";
2174
+ terminal.titleCarry = "";
2175
+ terminal.title = undefined;
2176
+ terminal.exit = undefined;
2177
+ terminal.cols = size.cols;
2178
+ terminal.rows = size.rows;
2179
+ for (const viewer of terminal.viewers)
2180
+ viewer.position = undefined;
2181
+ try {
2182
+ this.#start(terminal);
2183
+ } catch (error) {
2184
+ terminal.exit = { code: null };
2185
+ this.#changed(terminal.owner);
2186
+ throw error;
2187
+ }
2188
+ this.#changed(terminal.owner);
2189
+ }
2190
+ close(id) {
2191
+ const terminal = this.#get(id);
2192
+ this.#remove(terminal);
2193
+ this.#changed(terminal.owner);
2194
+ }
2195
+ closeOwners(owners) {
2196
+ for (const owner of new Set(owners)) {
2197
+ const owned = [...this.#terminals.values()].filter((terminal) => terminal.owner === owner);
2198
+ if (owned.length === 0)
2199
+ continue;
2200
+ for (const terminal of owned)
2201
+ this.#remove(terminal);
2202
+ this.#changed(owner);
2203
+ }
2204
+ }
2205
+ closeAll() {
2206
+ this.closeOwners([...this.#terminals.values()].map((terminal) => terminal.owner));
2207
+ }
2208
+ attach(id, since, sink) {
2209
+ const terminal = this.#get(id);
2210
+ const end = terminal.start + terminal.buffer.length;
2211
+ const position = since !== undefined && since >= terminal.start && since <= end ? since : undefined;
2212
+ const viewer = { sink, position, sending: false };
2213
+ terminal.viewers.add(viewer);
2214
+ this.#pump(terminal, viewer);
2215
+ return () => {
2216
+ terminal.viewers.delete(viewer);
2217
+ };
2218
+ }
2219
+ #get(id) {
2220
+ const terminal = this.#terminals.get(id);
2221
+ if (terminal === undefined)
2222
+ throw new Error("Unknown terminal");
2223
+ return terminal;
2224
+ }
2225
+ #start(terminal) {
2226
+ const generation = ++terminal.generation;
2227
+ const { shell, env } = this.#options;
2228
+ const pty = this.#options.spawn(shell.file, [...shell.args], {
2229
+ name: "xterm-256color",
2230
+ cols: terminal.cols,
2231
+ rows: terminal.rows,
2232
+ cwd: terminal.cwd,
2233
+ env: { ...env, TERM: "xterm-256color", COLORTERM: "truecolor" }
2234
+ });
2235
+ terminal.pty = pty;
2236
+ pty.onData((data) => {
2237
+ if (terminal.generation !== generation)
2238
+ return;
2239
+ terminal.pending += data;
2240
+ if (terminal.pending.length >= (this.#options.maxChunk ?? MAX_CHUNK))
2241
+ this.#flush(terminal);
2242
+ else
2243
+ terminal.flushTimer ??= setTimeout(() => this.#flush(terminal), this.#options.flushMs ?? FLUSH_MS);
2244
+ });
2245
+ pty.onExit(({ exitCode, signal }) => {
2246
+ if (terminal.generation !== generation || !this.#terminals.has(terminal.id))
2247
+ return;
2248
+ this.#flush(terminal);
2249
+ terminal.pty = undefined;
2250
+ terminal.exit = signal === undefined || signal === 0 ? { code: exitCode } : { code: exitCode, signal };
2251
+ this.#changed(terminal.owner);
2252
+ });
2253
+ }
2254
+ #stop(terminal) {
2255
+ if (terminal.flushTimer !== undefined)
2256
+ clearTimeout(terminal.flushTimer);
2257
+ terminal.flushTimer = undefined;
2258
+ terminal.generation++;
2259
+ const pty = terminal.pty;
2260
+ terminal.pty = undefined;
2261
+ try {
2262
+ pty?.kill();
2263
+ } catch {}
2264
+ }
2265
+ #remove(terminal) {
2266
+ this.#stop(terminal);
2267
+ this.#terminals.delete(terminal.id);
2268
+ for (const viewer of terminal.viewers)
2269
+ viewer.sink.ended("Terminal closed");
2270
+ terminal.viewers.clear();
2271
+ }
2272
+ #resize(terminal, size) {
2273
+ if (terminal.cols === size.cols && terminal.rows === size.rows)
2274
+ return;
2275
+ terminal.cols = size.cols;
2276
+ terminal.rows = size.rows;
2277
+ try {
2278
+ terminal.pty?.resize(size.cols, size.rows);
2279
+ } catch {}
2280
+ this.#changed(terminal.owner);
2281
+ }
2282
+ #flush(terminal) {
2283
+ if (terminal.flushTimer !== undefined)
2284
+ clearTimeout(terminal.flushTimer);
2285
+ terminal.flushTimer = undefined;
2286
+ const data = terminal.pending;
2287
+ if (data === "")
2288
+ return;
2289
+ terminal.pending = "";
2290
+ terminal.buffer += data;
2291
+ const bufferSize = this.#options.bufferSize ?? BUFFER_SIZE;
2292
+ if (terminal.buffer.length > bufferSize) {
2293
+ let cut = terminal.buffer.length - bufferSize;
2294
+ const line = terminal.buffer.indexOf(`
2295
+ `, cut);
2296
+ if (line !== -1 && line - cut < 1024)
2297
+ cut = line + 1;
2298
+ terminal.buffer = terminal.buffer.slice(cut);
2299
+ terminal.start += cut;
2300
+ }
2301
+ if (this.#readTitle(terminal, data))
2302
+ this.#changed(terminal.owner);
2303
+ for (const viewer of terminal.viewers)
2304
+ this.#pump(terminal, viewer);
2305
+ }
2306
+ #readTitle(terminal, data) {
2307
+ const text = terminal.titleCarry + data;
2308
+ let title;
2309
+ let consumed = 0;
2310
+ for (const match of text.matchAll(TITLE)) {
2311
+ title = match[1];
2312
+ consumed = match.index + match[0].length;
2313
+ }
2314
+ const open = text.lastIndexOf("\x1B]");
2315
+ terminal.titleCarry = open >= consumed && text.length - open <= MAX_TITLE ? text.slice(open) : "";
2316
+ if (title === undefined || title === terminal.title)
2317
+ return false;
2318
+ terminal.title = title;
2319
+ return terminal.rename === undefined;
2320
+ }
2321
+ async#pump(terminal, viewer) {
2322
+ if (viewer.sending)
2323
+ return;
2324
+ viewer.sending = true;
2325
+ try {
2326
+ const maxChunk = this.#options.maxChunk ?? MAX_CHUNK;
2327
+ while (terminal.viewers.has(viewer)) {
2328
+ const end = terminal.start + terminal.buffer.length;
2329
+ const reset = viewer.position === undefined || viewer.position < terminal.start;
2330
+ const from = reset ? terminal.start : viewer.position;
2331
+ if (!reset && from >= end)
2332
+ return;
2333
+ let to = reset ? end : Math.min(end, from + maxChunk);
2334
+ if (to < end && isHighSurrogate(terminal.buffer.charCodeAt(to - terminal.start - 1)))
2335
+ to--;
2336
+ const data = terminal.buffer.slice(from - terminal.start, to - terminal.start);
2337
+ viewer.position = to;
2338
+ await viewer.sink.output(reset ? { seq: to, data, reset: true } : { seq: to, data });
2339
+ }
2340
+ } finally {
2341
+ viewer.sending = false;
2342
+ }
2343
+ }
2344
+ #changed(owner) {
2345
+ const watchers = this.#watchers.get(owner);
2346
+ if (watchers === undefined)
2347
+ return;
2348
+ const list = this.list(owner);
2349
+ for (const watcher of watchers)
2350
+ watcher(list);
2351
+ }
2352
+ }
2353
+ async function startTerminals(options = {}) {
2354
+ let spawn3 = options.spawn;
2355
+ if (spawn3 === undefined) {
2356
+ try {
2357
+ const loaded = options.load === undefined ? await import("@lydell/node-pty") : await options.load();
2358
+ if (typeof loaded.spawn !== "function")
2359
+ throw new Error("the module has no spawn()");
2360
+ spawn3 = loaded.spawn;
2361
+ } catch (error) {
2362
+ const detail = error instanceof Error ? error.message : String(error);
2363
+ (options.diagnostic ?? console.error)(`Terminals unavailable: cannot load @lydell/node-pty on ${process.platform} (${process.arch}); missing @lydell/node-pty-${process.platform}-${process.arch}. Install without --omit=optional; the host continues without hello.terminals. (${detail})`);
2364
+ return;
2365
+ }
2366
+ }
2367
+ return new Terminals({ ...options, spawn: spawn3, shell: options.shell ?? loginShell(), env: options.env ?? shellEnv(process.env) });
2368
+ }
2369
+ function loginShell() {
2370
+ let file = process.env.SHELL;
2371
+ if (file === undefined || file === "") {
2372
+ try {
2373
+ file = userInfo().shell ?? undefined;
2374
+ } catch {
2375
+ file = undefined;
2376
+ }
2377
+ }
2378
+ return { file: file === undefined || file === "" ? "/bin/sh" : file, args: ["-l"] };
2379
+ }
2380
+ function shellEnv(env) {
2381
+ const result2 = {};
2382
+ for (const [key, value] of Object.entries(env)) {
2383
+ if (value !== undefined && !key.startsWith("PINOMAD_"))
2384
+ result2[key] = value;
2385
+ }
2386
+ return result2;
2387
+ }
2388
+ function info(terminal) {
2389
+ return {
2390
+ id: terminal.id,
2391
+ name: terminal.rename ?? terminal.title ?? `Terminal ${terminal.number}`,
2392
+ renamed: terminal.rename !== undefined,
2393
+ cols: terminal.cols,
2394
+ rows: terminal.rows,
2395
+ ...terminal.exit === undefined ? {} : { exit: terminal.exit }
2396
+ };
2397
+ }
2398
+ function isHighSurrogate(code) {
2399
+ return code >= 55296 && code <= 56319;
2400
+ }
2401
+
2057
2402
  // apps/host/src/folders.ts
2058
2403
  import { readdir, realpath as realpath5, stat as stat5 } from "node:fs/promises";
2059
2404
  import { isAbsolute, join as join6 } from "node:path";
@@ -3349,6 +3694,7 @@ async function startGateway(options) {
3349
3694
  const clients = new Set;
3350
3695
  let rtc;
3351
3696
  const rtcHello = () => rtc?.hello();
3697
+ const terminals = await startTerminals(options.terminals);
3352
3698
  const broadcast = (level, message) => {
3353
3699
  for (const client of clients)
3354
3700
  client.send({ type: "notice", level, message });
@@ -3363,10 +3709,12 @@ async function startGateway(options) {
3363
3709
  }
3364
3710
  rtc?.closeDevice(publicKey);
3365
3711
  };
3712
+ const reachableRelay = () => relay?.state().status === "removed" ? undefined : relay;
3366
3713
  const currentAdvertisement = () => {
3367
3714
  const advertiseUrl = remote?.advertiseUrl;
3715
+ const relay2 = reachableRelay();
3368
3716
  const endpoints = remoteEndpointCandidates({
3369
- ...relay === undefined ? {} : { relay: { origin: relay.origin, hostId: relay.hostId } },
3717
+ ...relay2 === undefined ? {} : { relay: { origin: relay2.origin, hostId: relay2.hostId } },
3370
3718
  ...advertiseUrl === undefined ? {} : { direct: { advertiseUrl } }
3371
3719
  });
3372
3720
  return { endpoints, ...advertiseUrl === undefined ? {} : { advertiseUrl } };
@@ -3383,6 +3731,7 @@ async function startGateway(options) {
3383
3731
  const client = new GatewayClient(connection, options, conversations, fileLister, skillLister, imageVariants, {
3384
3732
  devicePublicKey,
3385
3733
  rtcHello,
3734
+ terminals,
3386
3735
  closed: (self) => {
3387
3736
  clients.delete(self);
3388
3737
  connections.delete(self);
@@ -3403,11 +3752,12 @@ async function startGateway(options) {
3403
3752
  const fragment = (secret2, target2, endpointSet2) => `${pair(secret2)}&url=${encodeURIComponent(target2)}${endpointSet2 === undefined ? "" : `&endpointSet=${encodeEndpointSetParam(endpointSet2)}`}`;
3404
3753
  const advertisement = currentAdvertisement();
3405
3754
  const endpointSet = checkedEndpointSet(advertisement.endpoints);
3755
+ const relay2 = reachableRelay();
3406
3756
  const relayReachable = endpointSet.endpoints.some((endpoint) => endpoint.role === "relay");
3407
- if (relayReachable && relay !== undefined) {
3757
+ if (relayReachable && relay2 !== undefined) {
3408
3758
  const { secret: secret2, expiresAt: expiresAt2 } = spec.offers.create("qr");
3409
3759
  return {
3410
- url: `${relay.origin}/#${fragment(secret2, relaySocketTarget(relay.origin, relay.hostId), endpointSet)}`,
3760
+ url: `${relay2.origin}/#${fragment(secret2, relaySocketTarget(relay2.origin, relay2.hostId), endpointSet)}`,
3411
3761
  expiresAt: expiresAt2
3412
3762
  };
3413
3763
  }
@@ -3420,10 +3770,10 @@ async function startGateway(options) {
3420
3770
  const { secret: secret2, expiresAt: expiresAt2 } = spec.offers.create("qr");
3421
3771
  return { url: `${advertise}/#${fragment(secret2, direct.url, endpointSet)}`, expiresAt: expiresAt2 };
3422
3772
  }
3423
- if (relay !== undefined) {
3773
+ if (relay2 !== undefined) {
3424
3774
  const { secret: secret2, expiresAt: expiresAt2 } = spec.offers.create("qr");
3425
3775
  return {
3426
- url: `${relay.origin}/#${fragment(secret2, relaySocketTarget(relay.origin, relay.hostId))}`,
3776
+ url: `${relay2.origin}/#${fragment(secret2, relaySocketTarget(relay2.origin, relay2.hostId))}`,
3427
3777
  expiresAt: expiresAt2
3428
3778
  };
3429
3779
  }
@@ -3517,6 +3867,10 @@ async function startGateway(options) {
3517
3867
  });
3518
3868
  if (secure?.relay !== undefined) {
3519
3869
  const relayOrigin = secure.relay.origin;
3870
+ const startConfig = await readServerConfig(options.dataDir).catch(() => {
3871
+ return;
3872
+ });
3873
+ const linkJoin = startConfig !== undefined && normalizeOrigin(startConfig.origin) === normalizeOrigin(relayOrigin) ? startConfig : undefined;
3520
3874
  const currentBinding = async () => {
3521
3875
  const config = await readServerConfig(options.dataDir).catch(() => {
3522
3876
  return;
@@ -3570,6 +3924,16 @@ async function startGateway(options) {
3570
3924
  console.log(JSON.stringify({ event: "relay", status: "registered", origin: link.origin, hostId: link.hostId, push }));
3571
3925
  } else if (state.status === "connecting") {
3572
3926
  console.log(JSON.stringify({ event: "relay", status: "connecting", origin: link.origin }));
3927
+ } else if (state.status === "removed") {
3928
+ broadcast("warning", state.reason);
3929
+ console.error(state.reason);
3930
+ markRemoved(options.dataDir, {
3931
+ origin: relayOrigin,
3932
+ ...linkJoin?.binding === undefined ? {} : { binding: linkJoin.binding },
3933
+ ...linkJoin?.username === undefined ? {} : { username: linkJoin.username }
3934
+ }).catch((error) => {
3935
+ console.error(`could not write ${REMOVAL_NOTICE_FILE}: ${error instanceof Error ? error.message : String(error)}`);
3936
+ });
3573
3937
  } else {
3574
3938
  broadcast("warning", state.reason);
3575
3939
  console.error(state.reason);
@@ -3604,6 +3968,7 @@ async function startGateway(options) {
3604
3968
  detachSettingsWatch();
3605
3969
  await detachPush?.close();
3606
3970
  conversations.dispose();
3971
+ terminals?.closeAll();
3607
3972
  await rtc?.close();
3608
3973
  await Promise.all([...connections].map((connection) => connection.dispose()));
3609
3974
  for (const socket of server.clients)
@@ -3954,6 +4319,7 @@ class ConversationList {
3954
4319
  facts.label = subagent.label;
3955
4320
  if (typeof subagent?.role === "string" && subagent.role !== "")
3956
4321
  facts.role = subagent.role;
4322
+ facts.model = modelRefOf(subagent?.model);
3957
4323
  }
3958
4324
  facts.agent = roleNameOf(await harness.snapshot(RoleDoc, record.id, context3));
3959
4325
  const named = (await harness.snapshot(TitleDoc, record.id, context3))?.title;
@@ -4019,6 +4385,7 @@ class ConversationList {
4019
4385
  const value = change.value;
4020
4386
  facts.label = typeof value?.label === "string" && value.label !== "" ? value.label : undefined;
4021
4387
  facts.role = typeof value?.role === "string" && value.role !== "" ? value.role : undefined;
4388
+ facts.model = modelRefOf(value?.model);
4022
4389
  touched.add(id);
4023
4390
  } else if (change.record.kind === TitleDoc.definition.kind) {
4024
4391
  const named = change.value?.title;
@@ -4106,6 +4473,14 @@ function roleNameOf(value) {
4106
4473
  return;
4107
4474
  return typeof value.name === "string" ? value.name : value.name === null ? null : undefined;
4108
4475
  }
4476
+ function modelRefOf(value) {
4477
+ if (typeof value !== "string")
4478
+ return;
4479
+ const slash = value.indexOf("/");
4480
+ if (slash <= 0 || slash === value.length - 1)
4481
+ return;
4482
+ return { provider: value.slice(0, slash), modelId: value.slice(slash + 1) };
4483
+ }
4109
4484
  function rolesOf(index) {
4110
4485
  const roles = new Map;
4111
4486
  for (const entry of index?.conversations ?? []) {
@@ -4136,6 +4511,8 @@ function summaryOf(record, facts, role, currentAgent) {
4136
4511
  return {
4137
4512
  id: record.id,
4138
4513
  kind: record.owner !== undefined ? "subagent" : record.parent !== undefined ? "fork" : "conversation",
4514
+ ...record.owner === undefined ? {} : { lifecycle: "foreground" },
4515
+ ...facts.model === undefined ? {} : { model: facts.model },
4139
4516
  ...parent === undefined ? {} : { parent },
4140
4517
  ...record.parent === undefined ? {} : { forkedAt: String(record.parent.at) },
4141
4518
  ...(facts.named ?? facts.title) === undefined ? {} : { title: facts.named ?? facts.title },
@@ -4148,7 +4525,7 @@ function summaryOf(record, facts, role, currentAgent) {
4148
4525
  };
4149
4526
  }
4150
4527
  function sameSummary(a, b) {
4151
- return a.id === b.id && a.kind === b.kind && a.parent === b.parent && a.forkedAt === b.forkedAt && a.title === b.title && a.status === b.status && a.updatedAt === b.updatedAt && a.label === b.label && a.role === b.role && a.currentAgent === b.currentAgent && a.activity?.tool === b.activity?.tool && a.activity?.target === b.activity?.target;
4528
+ return a.id === b.id && a.kind === b.kind && a.lifecycle === b.lifecycle && a.model?.provider === b.model?.provider && a.model?.modelId === b.model?.modelId && a.parent === b.parent && a.forkedAt === b.forkedAt && a.title === b.title && a.status === b.status && a.updatedAt === b.updatedAt && a.label === b.label && a.role === b.role && a.currentAgent === b.currentAgent && a.activity?.tool === b.activity?.tool && a.activity?.target === b.activity?.target;
4152
4529
  }
4153
4530
  function titleOf(entry) {
4154
4531
  const message = entry?.model?.[0];
@@ -4291,6 +4668,7 @@ class GatewayClient {
4291
4668
  }
4292
4669
  #hostValues;
4293
4670
  #subscriptions = new Map;
4671
+ #terminalSizes = new Map;
4294
4672
  #disposed = false;
4295
4673
  constructor(connection, options, conversations, files, skills, imageVariants, hooks) {
4296
4674
  this.#connection = connection;
@@ -4353,7 +4731,8 @@ class GatewayClient {
4353
4731
  })),
4354
4732
  toolPresentations: options.toolPresentations ?? {},
4355
4733
  endpointSet: this.#hooks.endpointSet(),
4356
- ...rtc === undefined ? {} : { rtc }
4734
+ ...rtc === undefined ? {} : { rtc },
4735
+ ...this.#hooks.terminals === undefined ? {} : { terminals: { limit: this.#hooks.terminals.limit } }
4357
4736
  });
4358
4737
  }
4359
4738
  send(frame) {
@@ -4366,15 +4745,15 @@ class GatewayClient {
4366
4745
  }
4367
4746
  async#receive(frame) {
4368
4747
  if (frame.type === "subscribe")
4369
- await this.#subscribe(frame.stream, frame.images === "ref");
4748
+ await this.#subscribe(frame.stream, frame.images === "ref", frame.since);
4370
4749
  else if (frame.type === "unsubscribe")
4371
4750
  await this.#unsubscribe(frame.stream);
4372
4751
  else
4373
4752
  await this.#answer(frame.id, frame.method, frame.args);
4374
4753
  }
4375
- async#subscribe(stream, imageRefs) {
4754
+ async#subscribe(stream, imageRefs, since) {
4376
4755
  const previous = this.#subscriptions.get(stream);
4377
- const state = imageRefs && stream.startsWith("conversation:") ? { images: new ImageRefs } : {};
4756
+ const state = imageRefs && stream.startsWith("conversation:") ? { images: new ImageRefs } : since === undefined ? {} : { since };
4378
4757
  this.#subscriptions.set(stream, state);
4379
4758
  const current = () => !this.#disposed && this.#subscriptions.get(stream) === state;
4380
4759
  const send = (frame) => current() ? this.send(frame) : Promise.resolve();
@@ -4418,6 +4797,24 @@ class GatewayClient {
4418
4797
  }
4419
4798
  if (stream === "tasks")
4420
4799
  return this.#forward(stream, await this.#options.harness.watchTaskGraph(context3), send);
4800
+ if (stream.startsWith("terminals:")) {
4801
+ const terminals = this.#terminals();
4802
+ const conversation2 = await this.#conversation(Number(stream.slice("terminals:".length)));
4803
+ const { owner } = await this.#terminalOwner(conversation2);
4804
+ return { stop: terminals.watch(owner, (value) => void send({ type: "snapshot", stream, value })) };
4805
+ }
4806
+ if (stream.startsWith("terminal:")) {
4807
+ const terminalStream = stream;
4808
+ const detach = this.#terminals().attach(terminalStream.slice("terminal:".length), state?.since, {
4809
+ output: (frame) => send({ type: "output", stream: terminalStream, ...frame }),
4810
+ ended: (reason) => {
4811
+ send({ type: "ended", stream, reason });
4812
+ if (this.#subscriptions.get(stream) === state)
4813
+ this.#subscriptions.delete(stream);
4814
+ }
4815
+ });
4816
+ return { stop: detach };
4817
+ }
4421
4818
  if (stream.startsWith("doc:")) {
4422
4819
  const at = stream.lastIndexOf(":");
4423
4820
  return this.#openDoc(stream, stream.slice("doc:".length, at), Number(stream.slice(at + 1)), send);
@@ -4567,10 +4964,43 @@ class GatewayClient {
4567
4964
  await this.send({ type: "result", id, ok: true, value: value ?? null });
4568
4965
  } catch (error) {
4569
4966
  await this.send({ type: "result", id, ok: false, error: error instanceof Error ? error.message : String(error) });
4967
+ return;
4968
+ }
4969
+ const device = this.devicePublicKey;
4970
+ if (method === "revokeDevice" && device !== undefined && args.publicKey === device) {
4971
+ this.#hooks.closeDevice(device);
4570
4972
  }
4571
4973
  }
4572
4974
  async#call(method, args) {
4573
4975
  switch (method) {
4976
+ case "terminalInput": {
4977
+ const input = args;
4978
+ this.#terminals().input(input.terminalId, input.data, this.#terminalSizes.get(input.terminalId));
4979
+ return null;
4980
+ }
4981
+ case "resizeTerminal": {
4982
+ const input = args;
4983
+ const size = { cols: input.cols, rows: input.rows };
4984
+ this.#terminals().resize(input.terminalId, size);
4985
+ this.#terminalSizes.set(input.terminalId, size);
4986
+ return null;
4987
+ }
4988
+ case "renameTerminal": {
4989
+ const input = args;
4990
+ this.#terminals().rename(input.terminalId, input.name);
4991
+ return null;
4992
+ }
4993
+ case "restartTerminal": {
4994
+ const input = args;
4995
+ const size = { cols: input.cols, rows: input.rows };
4996
+ this.#terminals().restart(input.terminalId, size);
4997
+ this.#terminalSizes.set(input.terminalId, size);
4998
+ return null;
4999
+ }
5000
+ case "closeTerminal":
5001
+ this.#terminals().close(args.terminalId);
5002
+ this.#terminalSizes.delete(args.terminalId);
5003
+ return null;
4574
5004
  case "addProject":
4575
5005
  return addProject(this.#options.harness, args.path, context3);
4576
5006
  case "removeProject":
@@ -4634,6 +5064,7 @@ class GatewayClient {
4634
5064
  const input = args;
4635
5065
  await archive(this.#options.harness, input.conversationId, input.archived, context3);
4636
5066
  if (input.archived) {
5067
+ this.#hooks.terminals?.closeOwners(await conversationSubtree(this.#options.harness, input.conversationId, context3));
4637
5068
  const kept = await cleanupArchivedWorktrees(this.#options.harness, input.conversationId, context3);
4638
5069
  for (const { record, reason } of kept) {
4639
5070
  await this.send({ type: "notice", level: "warning", message: `Kept worktree ${record.path} (branch ${record.branch}): ${reason}` });
@@ -4650,7 +5081,8 @@ class GatewayClient {
4650
5081
  case "revokeDevice": {
4651
5082
  const { publicKey } = args;
4652
5083
  await revokeDevice(this.#options.harness, publicKey, context3);
4653
- this.#hooks.closeDevice(publicKey);
5084
+ if (publicKey !== this.devicePublicKey)
5085
+ this.#hooks.closeDevice(publicKey);
4654
5086
  return null;
4655
5087
  }
4656
5088
  case "rtcOffer": {
@@ -4933,8 +5365,34 @@ class GatewayClient {
4933
5365
  case "changes": {
4934
5366
  return await this.#changes(conversation, context3);
4935
5367
  }
5368
+ case "openTerminal": {
5369
+ const input = args;
5370
+ const terminals = this.#terminals();
5371
+ const { owner, cwd, record } = await this.#terminalOwner(conversation);
5372
+ const ready = record === undefined || await worktreeSettled(record);
5373
+ if (!ready || !await stat6(cwd).then((info2) => info2.isDirectory(), () => false)) {
5374
+ throw new Error(`The checkout is unavailable: ${cwd}`);
5375
+ }
5376
+ const size = { cols: input.cols, rows: input.rows };
5377
+ const terminalId = terminals.open(owner, cwd, size);
5378
+ this.#terminalSizes.set(terminalId, size);
5379
+ return { terminalId };
5380
+ }
4936
5381
  }
4937
5382
  }
5383
+ #terminals() {
5384
+ if (this.#hooks.terminals === undefined)
5385
+ throw new Error("Terminals are unavailable on this host");
5386
+ return this.#hooks.terminals;
5387
+ }
5388
+ async#terminalOwner(conversation) {
5389
+ const cwd = (await conversation.agent(context3)).cwd;
5390
+ if (cwd === undefined)
5391
+ throw new Error("No working directory");
5392
+ const record = checkoutAt((await this.#options.harness.snapshot(IndexDoc, context3))?.checkouts, cwd);
5393
+ const owner = record?.conversationId ?? await familyRoot(this.#options.harness, conversation.id, context3);
5394
+ return { owner, cwd, record };
5395
+ }
4938
5396
  async#expandSkills(text, scope, bound) {
4939
5397
  if (bound === undefined ? !text.includes("$") : bound.length === 0)
4940
5398
  return text;
@@ -5522,7 +5980,7 @@ import {
5522
5980
  readdir as readdir3,
5523
5981
  rename as rename2,
5524
5982
  rm as rm3,
5525
- stat as stat6,
5983
+ stat as stat7,
5526
5984
  writeFile as writeFile2
5527
5985
  } from "node:fs/promises";
5528
5986
  import { basename as basename2, join as join9, resolve as resolve6 } from "node:path";
@@ -5711,7 +6169,7 @@ class AttachmentStore {
5711
6169
  throw new Error(problem);
5712
6170
  const decoded = base64DecodedLength(data);
5713
6171
  const path = this.#dataPath(meta);
5714
- const received = (await stat6(path)).size;
6172
+ const received = (await stat7(path)).size;
5715
6173
  if (received === meta.size) {
5716
6174
  await chmod(path, 292);
5717
6175
  await this.#writeMeta({ ...meta, state: "complete" });
@@ -5754,13 +6212,13 @@ class AttachmentStore {
5754
6212
  if (meta.state === "uploading")
5755
6213
  throw new Error(`Attachment ${index + 1} is still uploading`);
5756
6214
  const path = this.#dataPath(meta);
5757
- let info;
6215
+ let info2;
5758
6216
  try {
5759
- info = await lstat(path);
6217
+ info2 = await lstat(path);
5760
6218
  } catch {
5761
6219
  throw new Error(`Attachment ${index + 1} is unavailable; attach it again`);
5762
6220
  }
5763
- if (!info.isFile() || info.size !== meta.size) {
6221
+ if (!info2.isFile() || info2.size !== meta.size) {
5764
6222
  throw new Error(`Attachment ${index + 1} is unavailable; attach it again`);
5765
6223
  }
5766
6224
  checked.push({ meta, path });
@@ -5803,7 +6261,7 @@ class AttachmentStore {
5803
6261
  } else {
5804
6262
  this.#pending.delete(entry.name);
5805
6263
  try {
5806
- if (now - (await stat6(directory)).mtimeMs <= this.#ttlMs)
6264
+ if (now - (await stat7(directory)).mtimeMs <= this.#ttlMs)
5807
6265
  continue;
5808
6266
  } catch {
5809
6267
  continue;
@@ -6002,6 +6460,7 @@ async function openHost(options) {
6002
6460
  }
6003
6461
  },
6004
6462
  ...options.rtc === undefined ? {} : { rtc: options.rtc },
6463
+ ...options.terminals === undefined ? {} : { terminals: options.terminals },
6005
6464
  secure: {
6006
6465
  hostKey,
6007
6466
  offers,
@@ -6083,7 +6542,7 @@ async function openHost(options) {
6083
6542
  }
6084
6543
 
6085
6544
  // apps/host/src/service/log-rotation.ts
6086
- import { copyFile, rename as rename3, rm as rm4, stat as stat7, truncate } from "node:fs/promises";
6545
+ import { copyFile, rename as rename3, rm as rm4, stat as stat8, truncate } from "node:fs/promises";
6087
6546
  import { join as join11 } from "node:path";
6088
6547
  var HOST_LOG_MAX_BYTES = 10 * 1024 * 1024;
6089
6548
  var HOST_LOG_HISTORY = 3;
@@ -6097,7 +6556,7 @@ async function rotateHostLog(dataDir, copy = copyFile) {
6097
6556
  await rm4(temp, { force: true });
6098
6557
  let size;
6099
6558
  try {
6100
- size = (await stat7(log)).size;
6559
+ size = (await stat8(log)).size;
6101
6560
  } catch (error) {
6102
6561
  if (isMissing(error))
6103
6562
  return false;
@@ -6145,7 +6604,7 @@ function startHostLogRotation(dataDir) {
6145
6604
  }
6146
6605
 
6147
6606
  // apps/host/src/model-auth.ts
6148
- import { randomUUID as randomUUID2 } from "node:crypto";
6607
+ import { randomUUID as randomUUID3 } from "node:crypto";
6149
6608
  import { mkdir as mkdir5, readFile as readFile6, rename as rename4, writeFile as writeFile4 } from "node:fs/promises";
6150
6609
  import { dirname as dirname3 } from "node:path";
6151
6610
  import { getBuiltinProviders } from "@earendil-works/pi-ai/providers/all";
@@ -6449,7 +6908,7 @@ async function openModelAuth(runtime, options) {
6449
6908
  if ([...logins.values()].some((login2) => login2.flow.providerId === providerId && login2.flow.state === "running")) {
6450
6909
  throw new Error(`A login for ${provider.name} is already in progress`);
6451
6910
  }
6452
- const id = randomUUID2();
6911
+ const id = randomUUID3();
6453
6912
  const login = {
6454
6913
  flow: { id, providerId, method, events: [], state: "running" },
6455
6914
  controller: new AbortController,
@@ -6824,7 +7283,7 @@ async function installId(dataDir) {
6824
7283
  const path = join12(dataDir, "install-id");
6825
7284
  let id = (await readFile7(path, "utf8").catch(() => "")).trim();
6826
7285
  if (id === "") {
6827
- id = randomUUID3();
7286
+ id = randomUUID4();
6828
7287
  await mkdir6(dataDir, { recursive: true });
6829
7288
  await writeFile5(path, `${id}
6830
7289
  `, { mode: 384 });