@nyte-ai/host 0.0.18 → 0.0.21

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 (103) hide show
  1. package/THIRD-PARTY-NOTICES.md +66 -0
  2. package/dist/chunks/{canvas-preview-rp6fz0w5.js → canvas-preview-2jwbe14k.js} +5 -5
  3. package/dist/chunks/{canvas-preview-j1tnf51w.js → canvas-preview-3a6tst4d.js} +1 -1
  4. package/dist/chunks/{canvas-preview-m02v4yy6.js → canvas-preview-55s0y68g.js} +3 -2
  5. package/dist/chunks/{canvas-preview-xn10d4f8.js → canvas-preview-5ekjx6sw.js} +9 -4
  6. package/dist/chunks/{canvas-preview-g3qj3y3k.js → canvas-preview-8hr08aeh.js} +13 -3
  7. package/dist/chunks/{canvas-preview-75db60xy.js → canvas-preview-8xc0vn7v.js} +5 -4
  8. package/dist/chunks/{canvas-preview-zn2d0k9d.js → canvas-preview-af86jz8t.js} +14 -6
  9. package/dist/chunks/{canvas-preview-d9wwhbtz.js → canvas-preview-aq5j4p25.js} +1 -1
  10. package/dist/chunks/{canvas-preview-ewgspmsd.js → canvas-preview-b3r1tna2.js} +1 -1
  11. package/dist/chunks/canvas-preview-befkjea2.js +60 -0
  12. package/dist/chunks/canvas-preview-bxnqt8cd.js +103 -0
  13. package/dist/chunks/{canvas-preview-senpjwep.js → canvas-preview-crmzxxt8.js} +3 -3
  14. package/dist/chunks/{canvas-preview-ymgz0937.js → canvas-preview-drctegz4.js} +8 -4
  15. package/dist/chunks/{canvas-preview-yrfz2ak6.js → canvas-preview-e3vnkkc5.js} +6 -6
  16. package/dist/chunks/canvas-preview-ec2mt7es.js +108 -0
  17. package/dist/chunks/{canvas-preview-0nrnbt76.js → canvas-preview-eehzfefa.js} +199 -125
  18. package/dist/chunks/{canvas-preview-5hxat7hh.js → canvas-preview-geg2gckp.js} +8 -6
  19. package/dist/chunks/{canvas-preview-8xgt67ka.js → canvas-preview-gh7c9831.js} +20 -16
  20. package/dist/chunks/{canvas-preview-1ta3avah.js → canvas-preview-k2xaz0ym.js} +10 -5
  21. package/dist/chunks/canvas-preview-kp75gb63.js +141 -0
  22. package/dist/chunks/{canvas-preview-evh92ywd.js → canvas-preview-mstbvwz3.js} +26 -11
  23. package/dist/chunks/{canvas-preview-h8hshnff.js → canvas-preview-pkzqg96v.js} +1 -1
  24. package/dist/chunks/{canvas-preview-ehf3vp31.js → canvas-preview-vm9zdp2t.js} +45 -58
  25. package/dist/chunks/{canvas-preview-76fwvd6e.js → canvas-preview-w54ymtb3.js} +1 -1
  26. package/dist/chunks/canvas-preview-y5y6ghaw.js +125 -0
  27. package/dist/chunks/{canvas-preview-vax9ne4e.js → canvas-preview-y7kq0c8q.js} +22 -11
  28. package/dist/chunks/{canvas-preview-98aj5p34.js → canvas-preview-ycepb05s.js} +1 -1
  29. package/dist/src/canvas-preview.js +7 -3
  30. package/dist/src/deploy/cloudflare/worker.js +1 -1
  31. package/dist/src/deploy/vercel/server/routes/[...].js +2 -2
  32. package/dist/src/deploy/vercel/server/utils/host.js +1 -1
  33. package/dist/src/deploy/vercel/server/workflows/drive.js +3 -3
  34. package/dist/src/images/exif.d.ts +1 -0
  35. package/dist/src/images/exif.js +6 -0
  36. package/dist/src/images/node.d.ts +37 -0
  37. package/dist/src/images/node.js +12 -0
  38. package/dist/src/images/photon.d.ts +3 -0
  39. package/dist/src/images/photon.js +7 -0
  40. package/dist/src/images/worker.d.ts +1 -0
  41. package/dist/src/images/worker.js +23 -0
  42. package/dist/src/index.d.ts +0 -1
  43. package/dist/src/index.js +23 -22
  44. package/dist/src/listen-fetch.js +3 -3
  45. package/dist/src/listen.js +4 -4
  46. package/dist/src/local/command.d.ts +5 -0
  47. package/dist/src/local/command.js +6 -2
  48. package/dist/src/local/files.js +2 -2
  49. package/dist/src/local/git-watch.d.ts +1 -1
  50. package/dist/src/local/git-watch.js +1 -1
  51. package/dist/src/local/git.js +3 -3
  52. package/dist/src/local/github.js +2 -2
  53. package/dist/src/local/jobs.d.ts +1 -1
  54. package/dist/src/local/jobs.js +2 -1
  55. package/dist/src/local/provider.js +9 -9
  56. package/dist/src/local/snapshots.js +2 -2
  57. package/dist/src/local/worktrees.js +2 -2
  58. package/dist/src/mcp/connect.d.ts +0 -13
  59. package/dist/src/mcp/connect.js +1 -1
  60. package/dist/src/mcp/hub.js +2 -2
  61. package/dist/src/open.d.ts +2 -30
  62. package/dist/src/open.js +20 -18
  63. package/dist/src/serve/binding.js +1 -1
  64. package/dist/src/serve/handler.js +2 -2
  65. package/dist/src/sqlite.d.ts +2 -1
  66. package/dist/src/sqlite.js +4 -3
  67. package/dist/src/store-usage.js +4 -1
  68. package/dist/src/usage-history.d.ts +54 -0
  69. package/dist/src/usage-history.js +14 -0
  70. package/dist/src/usage-service.d.ts +2 -1
  71. package/dist/src/usage-service.js +7 -3
  72. package/dist/src/usage-worker.js +8 -4
  73. package/dist/src/usage.d.ts +150 -27
  74. package/dist/src/usage.js +12 -2
  75. package/package.json +16 -8
  76. package/src/canvas-preview.ts +4 -3
  77. package/src/images/exif.ts +69 -0
  78. package/src/images/node.ts +163 -0
  79. package/src/images/photon.ts +141 -0
  80. package/src/images/worker.ts +29 -0
  81. package/src/index.ts +19 -1
  82. package/src/local/command.ts +64 -12
  83. package/src/local/files.ts +8 -3
  84. package/src/local/git-watch.ts +16 -6
  85. package/src/local/git.ts +7 -2
  86. package/src/local/github.ts +3 -4
  87. package/src/local/jobs.ts +55 -69
  88. package/src/local/snapshots.ts +4 -2
  89. package/src/local/worktrees.ts +4 -4
  90. package/src/mcp/connect.ts +1 -14
  91. package/src/mcp/hub.ts +18 -9
  92. package/src/open.ts +16 -36
  93. package/src/serve/binding.ts +4 -3
  94. package/src/sqlite.ts +46 -9
  95. package/src/store-usage.ts +5 -2
  96. package/src/usage-history.ts +219 -0
  97. package/src/usage-service.ts +12 -3
  98. package/src/usage.ts +251 -176
  99. package/dist/chunks/canvas-preview-a3yw0j70.js +0 -72
  100. package/dist/chunks/canvas-preview-azg56359.js +0 -68
  101. package/dist/src/store-owner.d.ts +0 -15
  102. package/dist/src/store-owner.js +0 -9
  103. package/src/store-owner.ts +0 -98
@@ -15,7 +15,7 @@ import {
15
15
  import {
16
16
  connectServer2,
17
17
  oauthSettings2
18
- } from "./canvas-preview-h8hshnff.js";
18
+ } from "./canvas-preview-pkzqg96v.js";
19
19
  import {
20
20
  toolExposure2,
21
21
  checkToolSchemas2,
@@ -44,7 +44,18 @@ function hub(input) {
44
44
  const contents = new Map;
45
45
  const watched = new Map;
46
46
  const userPath = join(input.home, ".nyte", "mcp.json");
47
+ const retiring = new Set;
47
48
  let closed = false;
49
+ const retire = (client) => {
50
+ if (client === null)
51
+ return Promise.resolve();
52
+ const done = client.close().catch(() => {
53
+ return;
54
+ });
55
+ retiring.add(done);
56
+ done.then(() => retiring.delete(done));
57
+ return done;
58
+ };
48
59
  const notify = (pool) => {
49
60
  for (const listener of pool.listeners)
50
61
  listener();
@@ -73,6 +84,7 @@ function hub(input) {
73
84
  let listed = null;
74
85
  let bridged = null;
75
86
  let client = null;
87
+ let opening = null;
76
88
  let stderr = () => null;
77
89
  let challenge = null;
78
90
  let ended = false;
@@ -104,9 +116,7 @@ function hub(input) {
104
116
  const drop = (message) => {
105
117
  const previous = client;
106
118
  client = null;
107
- previous?.close().catch(() => {
108
- return;
109
- });
119
+ retire(previous);
110
120
  if (ended)
111
121
  return;
112
122
  set({ kind: "failed", message, stderr: stderr() });
@@ -118,11 +128,7 @@ function hub(input) {
118
128
  await processEnvironment();
119
129
  if (ended)
120
130
  return;
121
- const next = new TracingMcpClient2({
122
- name: "nyte",
123
- version: "0.1.0",
124
- requestTimeoutMs: link.entry.timeoutMs
125
- });
131
+ const next = new TracingMcpClient2({ name: "nyte", version: "0.1.0" });
126
132
  next.onClose(() => {
127
133
  if (client === next)
128
134
  drop(`The connection to ${entry.name} closed.`);
@@ -143,9 +149,10 @@ function hub(input) {
143
149
  }
144
150
  try {
145
151
  stderr = () => transport instanceof StdioTransport && transport.stderr !== "" ? transport.stderr.slice(-STDERR_TAIL) : null;
152
+ opening = next;
146
153
  await next.connect(transport);
147
154
  if (ended) {
148
- await next.close();
155
+ await retire(next);
149
156
  return;
150
157
  }
151
158
  const refresh = async () => {
@@ -171,9 +178,7 @@ function hub(input) {
171
178
  } catch (error) {
172
179
  if (client === next)
173
180
  client = null;
174
- await next.close().catch(() => {
175
- return;
176
- });
181
+ await retire(next);
177
182
  if (ended)
178
183
  return;
179
184
  if (error instanceof McpOAuthAuthorizationRequiredError) {
@@ -223,9 +228,7 @@ function hub(input) {
223
228
  clearTimeout(timer);
224
229
  const previous = client;
225
230
  client = null;
226
- await previous?.close().catch(() => {
227
- return;
228
- });
231
+ await Promise.all([retire(previous), retire(opening)]);
229
232
  }
230
233
  };
231
234
  connectTraced();
@@ -553,6 +556,7 @@ function hub(input) {
553
556
  for (const pool of live)
554
557
  shut(pool);
555
558
  await Promise.all(links.map((link) => link.close()));
559
+ await Promise.all(retiring);
556
560
  }
557
561
  };
558
562
  }
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  readNyteUsage2
3
- } from "./canvas-preview-5hxat7hh.js";
3
+ } from "./canvas-preview-geg2gckp.js";
4
4
  import {
5
- createUsageScanCaches2,
5
+ loadUsageScanCaches2,
6
+ saveUsageScanCaches2,
6
7
  readLocalUsage2
7
- } from "./canvas-preview-0nrnbt76.js";
8
+ } from "./canvas-preview-eehzfefa.js";
8
9
 
9
10
  // src/usage-service.ts
10
11
  import { join } from "node:path";
@@ -17,11 +18,13 @@ function catalogForUsage2(models) {
17
18
 
18
19
  class UsageScanner2 {
19
20
  home;
20
- caches = createUsageScanCaches2();
21
+ cachePath;
22
+ caches;
21
23
  models;
22
24
  scans = serial();
23
25
  constructor(home) {
24
26
  this.home = home;
27
+ this.cachePath = join(home, ".nyte", "usage-scan-cache.json");
25
28
  this.models = createModels({
26
29
  modelsStore: new FileModelsStore(join(home, ".nyte", "models-store.json"))
27
30
  });
@@ -39,8 +42,10 @@ class UsageScanner2 {
39
42
  const models = catalog === undefined ? this.models : {
40
43
  getModels: (provider) => catalog.filter((model) => provider === undefined || model.provider === provider)
41
44
  };
42
- const local = await readLocalUsage2({ models, caches: this.caches });
45
+ const caches = this.caches ??= await loadUsageScanCaches2(this.cachePath);
46
+ const local = await readLocalUsage2({ models, window: request.window, caches });
43
47
  const report = readNyteUsage2(this.home, request.window);
48
+ this.scans.run(() => saveUsageScanCaches2(this.cachePath, caches));
44
49
  return {
45
50
  ...report,
46
51
  ...local,
@@ -0,0 +1,141 @@
1
+ // src/usage-history.ts
2
+ import { Buffer } from "node:buffer";
3
+ import { randomUUID } from "node:crypto";
4
+ import { constants } from "node:fs";
5
+ import { mkdir, open, readFile, rename, rm, writeFile } from "node:fs/promises";
6
+ import { dirname } from "node:path";
7
+ import { Type } from "typebox";
8
+ function fileScanSchema2(scan) {
9
+ return Type.Object({
10
+ size: Type.Number({ minimum: 0 }),
11
+ mtimeMs: Type.Number(),
12
+ position: Type.Number({ minimum: 0 }),
13
+ committed: scan
14
+ });
15
+ }
16
+
17
+ class UsageScanCache2 {
18
+ #files;
19
+ #changed = false;
20
+ constructor(files = []) {
21
+ this.#files = new Map(files);
22
+ }
23
+ keys() {
24
+ return this.#files.keys();
25
+ }
26
+ entries() {
27
+ return this.#files.entries();
28
+ }
29
+ get(path) {
30
+ return this.#files.get(path);
31
+ }
32
+ set(path, scan) {
33
+ if (this.#files.get(path) === scan)
34
+ return;
35
+ this.#files.set(path, scan);
36
+ this.#changed = true;
37
+ }
38
+ keepOnly(seen) {
39
+ for (const path of this.#files.keys()) {
40
+ if (seen.has(path))
41
+ continue;
42
+ this.#files.delete(path);
43
+ this.#changed = true;
44
+ }
45
+ }
46
+ get changed() {
47
+ return this.#changed;
48
+ }
49
+ markSaved() {
50
+ this.#changed = false;
51
+ }
52
+ }
53
+ async function readHistory2(path, info, parser, cache, signal) {
54
+ const hit = cache?.get(path);
55
+ const unchanged = hit !== undefined && hit.size === info.size && hit.mtimeMs === info.mtimeMs;
56
+ if (unchanged && hit.position === info.size)
57
+ return hit.committed;
58
+ const from = unchanged || hit !== undefined && info.size > hit.size ? hit : undefined;
59
+ const fold = parser(from?.committed);
60
+ let position = from?.position ?? 0;
61
+ let unterminated;
62
+ for await (const line of historyLines(path, position, signal)) {
63
+ signal?.throwIfAborted();
64
+ if (!line.terminated) {
65
+ unterminated = line;
66
+ continue;
67
+ }
68
+ fold.line(line);
69
+ position = line.end;
70
+ }
71
+ const committed = fold.scan();
72
+ cache?.set(path, unchanged ? hit : { size: info.size, mtimeMs: info.mtimeMs, position, committed });
73
+ if (unterminated === undefined)
74
+ return committed;
75
+ fold.line(unterminated);
76
+ return fold.scan();
77
+ }
78
+ async function* historyLines(path, start, signal) {
79
+ const file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
80
+ try {
81
+ const stat = await file.stat();
82
+ if (!stat.isFile())
83
+ throw new Error("History is not a regular file.");
84
+ const buffer = Buffer.alloc(64 * 1024);
85
+ let pending = Buffer.alloc(0);
86
+ let pendingStart = start;
87
+ let position = start;
88
+ while (position < stat.size) {
89
+ signal?.throwIfAborted();
90
+ const { bytesRead } = await file.read(buffer, 0, Math.min(buffer.length, stat.size - position), position);
91
+ if (bytesRead === 0)
92
+ break;
93
+ position += bytesRead;
94
+ const read = buffer.subarray(0, bytesRead);
95
+ const chunk = pending.length === 0 ? read : Buffer.concat([pending, read]);
96
+ let from = 0;
97
+ for (let newline = chunk.indexOf(10);newline !== -1; newline = chunk.indexOf(10, from)) {
98
+ yield {
99
+ text: chunk.toString("utf8", from, newline),
100
+ terminated: true,
101
+ end: pendingStart + newline + 1
102
+ };
103
+ from = newline + 1;
104
+ }
105
+ pendingStart += from;
106
+ pending = Buffer.from(chunk.subarray(from));
107
+ }
108
+ if (pending.length > 0)
109
+ yield {
110
+ text: pending.toString("utf8"),
111
+ terminated: false,
112
+ end: pendingStart + pending.length
113
+ };
114
+ } finally {
115
+ await file.close();
116
+ }
117
+ }
118
+ async function readCacheDocument2(path, validator) {
119
+ try {
120
+ const value = JSON.parse(await readFile(path, "utf8"));
121
+ return validator.Check(value) ? value : undefined;
122
+ } catch {
123
+ return;
124
+ }
125
+ }
126
+ async function writeCacheDocument2(path, document) {
127
+ const temporary = `${path}.${randomUUID()}.tmp`;
128
+ try {
129
+ await mkdir(dirname(path), { recursive: true });
130
+ await writeFile(temporary, JSON.stringify(document), { mode: 384 });
131
+ await rename(temporary, path);
132
+ return true;
133
+ } catch {
134
+ await rm(temporary, { force: true }).catch(() => {
135
+ return;
136
+ });
137
+ return false;
138
+ }
139
+ }
140
+
141
+ export { fileScanSchema2, UsageScanCache2, readHistory2, readCacheDocument2, writeCacheDocument2 };
@@ -18,13 +18,11 @@ import {
18
18
  sessionIndexChanges2,
19
19
  sqlSessionIndex2
20
20
  } from "./canvas-preview-0cmbkp2j.js";
21
- import {
22
- claimStore2
23
- } from "./canvas-preview-a3yw0j70.js";
24
21
 
25
22
  // src/sqlite.ts
26
23
  import { mkdirSync, readdirSync, rmSync, writeFileSync } from "node:fs";
27
24
  import { dirname, join } from "node:path";
25
+ import process from "node:process";
28
26
  import { DatabaseSync } from "node:sqlite";
29
27
  import { asSeq } from "@nyte-ai/schema";
30
28
  import { dev } from "@nyte-ai/core";
@@ -43,14 +41,15 @@ import {
43
41
  var BUSY_TIMEOUT_MS = 5000;
44
42
  var BACKUPS_KEPT = 3;
45
43
  var PUT_BODY = "INSERT INTO objects (id, body) VALUES (?, ?) ON CONFLICT (id) DO UPDATE SET body = excluded.body WHERE objects.body IS NULL";
44
+ function storePath2(home) {
45
+ return process.env["NYTE_DB"] ?? join(home, ".nyte", "nyte.db");
46
+ }
46
47
  function sqliteStore2(input) {
47
48
  let database = null;
48
49
  let opening = null;
49
50
  let closing = null;
50
51
  let workers = null;
51
- let release = null;
52
52
  const open = async () => {
53
- const claim = "path" in input ? claimStore2(input.path) : null;
54
53
  let created;
55
54
  try {
56
55
  created = new DatabaseSync("path" in input ? input.path : ":memory:", {
@@ -65,11 +64,9 @@ function sqliteStore2(input) {
65
64
  created.exec("PRAGMA optimize = 0x10012");
66
65
  } catch (error) {
67
66
  created?.close();
68
- claim?.();
69
67
  throw error;
70
68
  }
71
69
  database = created;
72
- release = claim;
73
70
  workers = "path" in input ? sqliteWorkers2(created, input.path) : null;
74
71
  return connect(created, workers);
75
72
  };
@@ -90,11 +87,10 @@ function sqliteStore2(input) {
90
87
  return;
91
88
  });
92
89
  await workers?.close();
93
- database?.close();
94
- release?.();
90
+ if (database !== null)
91
+ closeDatabase(database);
95
92
  workers = null;
96
93
  database = null;
97
- release = null;
98
94
  opening = null;
99
95
  })().finally(() => {
100
96
  closing = null;
@@ -103,6 +99,15 @@ function sqliteStore2(input) {
103
99
  }
104
100
  };
105
101
  }
102
+ function closeDatabase(database) {
103
+ if ("bun" in process.versions) {
104
+ const empty = new DatabaseSync(":memory:");
105
+ empty.exec("CREATE TABLE closed (id INTEGER)");
106
+ database.deserialize(empty.serialize());
107
+ empty.close();
108
+ }
109
+ database.close();
110
+ }
106
111
  function hasTable(database, name) {
107
112
  return database.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(name) !== undefined;
108
113
  }
@@ -219,6 +224,7 @@ function connect(database, workers) {
219
224
  const listRange = database.prepare("SELECT name, target, revision FROM refs WHERE name >= ? AND name < ? ORDER BY name");
220
225
  const putRef = database.prepare("INSERT INTO refs (name, target, revision) VALUES (?, ?, ?) ON CONFLICT (name) DO UPDATE SET target = excluded.target, revision = excluded.revision");
221
226
  const deleteRef = database.prepare("DELETE FROM refs WHERE name = ?");
227
+ const audit = auditing(database);
222
228
  const append = database.prepare("INSERT INTO log (session, seq, at, event) VALUES (?, ?, ?, ?)");
223
229
  const readLog = database.prepare("SELECT seq, at, event FROM log WHERE session = ? AND seq > ? ORDER BY seq LIMIT ?");
224
230
  const readTail = database.prepare("SELECT MAX(seq) AS tail FROM log WHERE session = ?");
@@ -295,12 +301,15 @@ function connect(database, workers) {
295
301
  };
296
302
  }
297
303
  const touched = new Map;
304
+ const current = new Map([...before].map(([name, row]) => [name, row?.revision ?? null]));
298
305
  const revisions = updates.map((update) => {
299
306
  const revision = mintRevision2();
300
307
  if (update.target === null)
301
308
  deleteRef.run(update.name);
302
309
  else
303
310
  putRef.run(update.name, update.target, revision);
311
+ audit?.run(Date.now(), process.pid, fenceNames([update]).find((name) => name.startsWith("processes/")) ?? null, update.name, current.get(update.name) ?? null, update.target === null ? null : revision, update.target);
312
+ current.set(update.name, update.target === null ? null : revision);
304
313
  updateSessionIndex(database, update.name, update.target, revision);
305
314
  const session = parseSessionRef(update.name);
306
315
  if (session !== null)
@@ -354,6 +363,12 @@ function connect(database, workers) {
354
363
  }
355
364
  };
356
365
  }
366
+ function auditing(database) {
367
+ if (process.env.NYTE_STORE_AUDIT !== "1")
368
+ return null;
369
+ database.exec("CREATE TABLE IF NOT EXISTS store_audit (id INTEGER PRIMARY KEY, at INTEGER NOT NULL, pid INTEGER NOT NULL, writer TEXT, name TEXT NOT NULL, before TEXT, after TEXT, target TEXT)");
370
+ return database.prepare("INSERT INTO store_audit (at, pid, writer, name, before, after, target) VALUES (?, ?, ?, ?, ?, ?, ?)");
371
+ }
357
372
  function updateSessionIndex(database, name, target, revision) {
358
373
  if (sessionIndexRef(name) === null)
359
374
  return;
@@ -366,4 +381,4 @@ function write(database, statement, ...params) {
366
381
  return (size > 64 * 1024 ? database.prepare(statement.sourceSQL) : statement).run(...params);
367
382
  }
368
383
 
369
- export { sqliteStore2 };
384
+ export { storePath2, sqliteStore2 };
@@ -54,7 +54,7 @@ function expand(values, server) {
54
54
  value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_match, name) => {
55
55
  const found = process.env[name];
56
56
  if (found === undefined)
57
- throw new Error(`${server} needs the environment variable ${name}. Set it where Nyte runs, then restart Nyte (for the CLI, \`nyte service stop\`).`);
57
+ throw new Error(`${server} needs the environment variable ${name}. Set it where Nyte runs, then restart Nyte.`);
58
58
  return found;
59
59
  })
60
60
  ]));
@@ -1,7 +1,14 @@
1
+ import {
2
+ run2,
3
+ spawnCommand2,
4
+ killTree2,
5
+ findExecutable2
6
+ } from "./canvas-preview-bxnqt8cd.js";
7
+
1
8
  // src/local/jobs.ts
2
9
  import { spawn } from "node:child_process";
3
10
  import { createRequire } from "node:module";
4
- import { resolve } from "node:path";
11
+ import { basename, resolve } from "node:path";
5
12
  import { asId, SdkError } from "@nyte-ai/schema";
6
13
  import {
7
14
  processEnvironment,
@@ -16,11 +23,7 @@ var PAUSE_BYTES = 256 * 1024;
16
23
  var RESUME_BYTES = 64 * 1024;
17
24
  var STOP_GRACE_MS = 2000;
18
25
  var CLOSE_GRACE_MS = 2000;
19
- var QUIET_MS = 300;
20
26
  var DONE = { done: true, value: undefined };
21
- var ESC = "\x1B";
22
- var BEL = "\x07";
23
- var PROMPT_MARKER = new RegExp(`${ESC}\\]133;([ABCD])`, "g");
24
27
  var loaded;
25
28
  function localExec2(root, options) {
26
29
  const readers = new Set;
@@ -106,7 +109,7 @@ function localExec2(root, options) {
106
109
  return { kind: "not-pty" };
107
110
  return finished === null && driver.resize(size) ? { kind: "resized" } : { kind: "missing" };
108
111
  },
109
- idle: async () => finished === null && driver.idle(),
112
+ idle: async () => driver.pty && (finished !== null || driver.idle()),
110
113
  stop: async () => ({ kind: "settled", outcome: await stop() }),
111
114
  settled: (request) => waitEnd(ended, request),
112
115
  close,
@@ -116,16 +119,16 @@ function localExec2(root, options) {
116
119
  };
117
120
  }
118
121
  async function pipe(command, options, log, finish) {
119
- const group = process.platform !== "win32";
122
+ const windows = process.platform === "win32";
120
123
  const [file, ...args] = typeof command === "string" ? [command] : command;
121
124
  const settings = {
122
125
  cwd: options.cwd,
123
126
  env: options.env,
124
- detached: group,
127
+ detached: !windows,
125
128
  stdio: "pipe",
126
129
  windowsHide: true
127
130
  };
128
- const child = typeof command === "string" ? spawn(command, { ...settings, shell: true }) : spawn(file ?? "", args, settings);
131
+ const child = typeof command === "string" ? spawn(command, { ...settings, shell: true }) : spawnCommand2(windows ? await findExecutable2(file ?? "") ?? file ?? "" : file ?? "", args, settings);
129
132
  await new Promise((started, failed) => {
130
133
  child.once("spawn", started);
131
134
  child.once("error", (error) => failed(new SdkError("unavailable", `Cannot start ${file ?? ""}: ${error.message}`)));
@@ -146,12 +149,13 @@ async function pipe(command, options, log, finish) {
146
149
  child.stdin?.on("error", () => {
147
150
  return;
148
151
  });
149
- child.on("close", (code) => finish(code));
152
+ let killed = false;
153
+ child.on("close", (code) => finish(killed ? null : code));
150
154
  child.on("exit", (code) => {
151
155
  setTimeout(() => {
152
156
  child.stdout?.destroy();
153
157
  child.stderr?.destroy();
154
- finish(code);
158
+ finish(killed ? null : code);
155
159
  }, CLOSE_GRACE_MS).unref();
156
160
  });
157
161
  return {
@@ -165,8 +169,13 @@ async function pipe(command, options, log, finish) {
165
169
  resize: () => false,
166
170
  idle: () => false,
167
171
  kill(signal) {
172
+ if (windows) {
173
+ killed = true;
174
+ killTree2(child, signal);
175
+ return;
176
+ }
168
177
  try {
169
- if (group && child.pid !== undefined)
178
+ if (child.pid !== undefined)
170
179
  process.kill(-child.pid, signal);
171
180
  else
172
181
  child.kill(signal);
@@ -200,21 +209,7 @@ async function terminal(command, options, log, finish) {
200
209
  throw new SdkError("unavailable", `Cannot start ${file ?? shell} in a pty: ${error instanceof Error ? error.message : String(error)}`);
201
210
  }
202
211
  const encoder = new TextEncoder;
203
- let marker = null;
204
- let line = "";
205
- let quietSince = 0;
206
- term.onData((text) => {
207
- log.push(encoder.encode(text));
208
- quietSince = Date.now();
209
- marker = lastMarker(text) ?? marker;
210
- const plain = withoutEscapes(text);
211
- const broken = plain.lastIndexOf(`
212
- `);
213
- line = (broken < 0 ? line + plain : plain.slice(broken + 1)).slice(-1024);
214
- const returned = line.replace(/\r+$/, "").lastIndexOf("\r");
215
- if (returned >= 0)
216
- line = line.slice(returned + 1);
217
- });
212
+ term.onData((text) => log.push(encoder.encode(text)));
218
213
  term.onExit(({ exitCode, signal }) => finish(signal ? null : exitCode));
219
214
  log.throttle({ pause: () => term.pause(), resume: () => term.resume() });
220
215
  return {
@@ -226,10 +221,29 @@ async function terminal(command, options, log, finish) {
226
221
  term.resize(size.columns, size.rows);
227
222
  return true;
228
223
  },
229
- idle() {
230
- if (marker !== null)
231
- return marker !== "C";
232
- return quietSince > 0 && Date.now() - quietSince >= QUIET_MS && line.trim() !== "";
224
+ async idle() {
225
+ if (typeof command === "string" || command.length !== 0)
226
+ return false;
227
+ const result = await run2(windows ? "powershell.exe" : "ps", windows ? [
228
+ "-NoProfile",
229
+ "-NonInteractive",
230
+ "-Command",
231
+ `Get-CimInstance Win32_Process -Filter "ParentProcessId = ${term.pid} AND Name != 'conhost.exe' AND Name != 'OpenConsole.exe'" | Select-Object -ExpandProperty ProcessId`
232
+ ] : ["-A", "-o", "pid=,ppid=,stat=,comm="], { cwd: options.cwd, timeoutMs: 1500 });
233
+ if (result.code !== 0)
234
+ throw new SdkError("unavailable", "Cannot inspect terminal processes");
235
+ const processes = result.stdout.toString("utf8").trim();
236
+ if (windows)
237
+ return processes === "";
238
+ return !processes.split(`
239
+ `).some((line) => {
240
+ const [pid, parent, state, ...name] = line.trim().split(/\s+/);
241
+ if (state?.startsWith("Z"))
242
+ return false;
243
+ if (Number(parent) === term.pid)
244
+ return true;
245
+ return Number(pid) === term.pid && basename(name.join(" ")).replace(/^-/, "") !== basename(shell);
246
+ });
233
247
  },
234
248
  kill(signal) {
235
249
  try {
@@ -238,33 +252,6 @@ async function terminal(command, options, log, finish) {
238
252
  }
239
253
  };
240
254
  }
241
- function lastMarker(text) {
242
- return Array.from(text.matchAll(PROMPT_MARKER), (match) => match[1]).at(-1) ?? null;
243
- }
244
- function withoutEscapes(text) {
245
- let plain = "";
246
- let at = 0;
247
- while (at < text.length) {
248
- const char = text[at] ?? "";
249
- if (char !== ESC) {
250
- plain += char;
251
- at += 1;
252
- continue;
253
- }
254
- const kind = text[at + 1] ?? "";
255
- at += 2;
256
- if (kind === "[") {
257
- while (at < text.length && !/[@-~]/.test(text[at] ?? ""))
258
- at += 1;
259
- at += 1;
260
- } else if (kind === "]") {
261
- while (at < text.length && text[at] !== BEL && !(text[at] === ESC && text[at + 1] === "\\"))
262
- at += 1;
263
- at += text[at] === BEL ? 1 : 2;
264
- }
265
- }
266
- return plain;
267
- }
268
255
  function loadPty() {
269
256
  if (loaded === undefined)
270
257
  loaded = ptyFrom(() => createRequire(import.meta.url)("@lydell/node-pty"));
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createHandler2
3
- } from "./canvas-preview-d9wwhbtz.js";
3
+ } from "./canvas-preview-aq5j4p25.js";
4
4
 
5
5
  // src/listen-fetch.ts
6
6
  import { SdkError } from "@nyte-ai/schema";