@ericsanchezok/meta-synergy 0.0.0-dev-202603260728

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 (48) hide show
  1. package/.turbo/turbo-typecheck.log +1 -0
  2. package/dist/meta-protocol/src/bash.d.ts +89 -0
  3. package/dist/meta-protocol/src/bash.js +40 -0
  4. package/dist/meta-protocol/src/client.d.ts +9 -0
  5. package/dist/meta-protocol/src/client.js +1 -0
  6. package/dist/meta-protocol/src/env.d.ts +16 -0
  7. package/dist/meta-protocol/src/env.js +17 -0
  8. package/dist/meta-protocol/src/envelope.d.ts +50 -0
  9. package/dist/meta-protocol/src/envelope.js +23 -0
  10. package/dist/meta-protocol/src/error.d.ts +39 -0
  11. package/dist/meta-protocol/src/error.js +24 -0
  12. package/dist/meta-protocol/src/host.d.ts +90 -0
  13. package/dist/meta-protocol/src/host.js +27 -0
  14. package/dist/meta-protocol/src/index.d.ts +7 -0
  15. package/dist/meta-protocol/src/index.js +7 -0
  16. package/dist/meta-protocol/src/process.d.ts +274 -0
  17. package/dist/meta-protocol/src/process.js +89 -0
  18. package/dist/meta-synergy/src/client/holos-client.d.ts +15 -0
  19. package/dist/meta-synergy/src/client/holos-client.js +35 -0
  20. package/dist/meta-synergy/src/exec/bash-runner.d.ts +7 -0
  21. package/dist/meta-synergy/src/exec/bash-runner.js +9 -0
  22. package/dist/meta-synergy/src/exec/process-registry.d.ts +11 -0
  23. package/dist/meta-synergy/src/exec/process-registry.js +597 -0
  24. package/dist/meta-synergy/src/host.d.ts +32 -0
  25. package/dist/meta-synergy/src/host.js +27 -0
  26. package/dist/meta-synergy/src/index.d.ts +8 -0
  27. package/dist/meta-synergy/src/index.js +8 -0
  28. package/dist/meta-synergy/src/platform.d.ts +25 -0
  29. package/dist/meta-synergy/src/platform.js +230 -0
  30. package/dist/meta-synergy/src/rpc/handler.d.ts +66 -0
  31. package/dist/meta-synergy/src/rpc/handler.js +60 -0
  32. package/dist/meta-synergy/src/rpc/schema.d.ts +163 -0
  33. package/dist/meta-synergy/src/rpc/schema.js +11 -0
  34. package/dist/meta-synergy/src/types.d.ts +14 -0
  35. package/dist/meta-synergy/src/types.js +1 -0
  36. package/package.json +30 -0
  37. package/script/publish.ts +32 -0
  38. package/src/client/holos-client.ts +49 -0
  39. package/src/exec/bash-runner.ts +10 -0
  40. package/src/exec/process-registry.ts +728 -0
  41. package/src/host.ts +39 -0
  42. package/src/index.ts +8 -0
  43. package/src/platform.ts +227 -0
  44. package/src/rpc/handler.ts +76 -0
  45. package/src/rpc/schema.ts +16 -0
  46. package/src/types.ts +17 -0
  47. package/test/rpc-handler.test.ts +76 -0
  48. package/tsconfig.json +23 -0
@@ -0,0 +1,597 @@
1
+ import process from "node:process";
2
+ import { spawn } from "node:child_process";
3
+ import { Platform } from "../platform";
4
+ const MAX_OUTPUT_CHARS = 200_000;
5
+ const TAIL_CHARS = 2_000;
6
+ const DEFAULT_TTL_MS = 30 * 60 * 1000;
7
+ export class ProcessRegistry {
8
+ #running = new Map();
9
+ #finished = new Map();
10
+ #waiters = new Map();
11
+ #ttlMs;
12
+ #host;
13
+ #sweeper;
14
+ constructor(host, options) {
15
+ this.#host = host;
16
+ this.#ttlMs = Math.max(60_000, options?.ttlMs ?? DEFAULT_TTL_MS);
17
+ }
18
+ async executeBash(request, envID) {
19
+ this.#host.assertEnv(envID);
20
+ const launched = this.#launchShellProcess({
21
+ command: request.command,
22
+ description: request.description,
23
+ workdir: Platform.resolveWorkdir(request.workdir),
24
+ timeoutMs: request.timeout,
25
+ });
26
+ if (request.background) {
27
+ launched.record.backgrounded = true;
28
+ return this.#backgroundResult(launched.record, envID, request.description, "Background");
29
+ }
30
+ if (request.yieldMs && request.yieldMs > 0) {
31
+ const autoBackground = await Promise.race([
32
+ this.#waitForExit(launched.record.processId).then(() => false),
33
+ Platform.sleep(request.yieldMs).then(() => !launched.record.exited),
34
+ ]);
35
+ if (autoBackground) {
36
+ launched.record.backgrounded = true;
37
+ return this.#backgroundResult(launched.record, envID, request.description, "Auto-Background", request.yieldMs);
38
+ }
39
+ }
40
+ await this.#waitForExit(launched.record.processId);
41
+ const current = this.#getCurrentOrFinished(launched.record.processId);
42
+ const runtimeMs = this.#runtimeMs(current ?? launched.record);
43
+ const output = current?.output ?? launched.record.output;
44
+ const exitCode = current?.exitCode ?? launched.record.exitCode ?? null;
45
+ const timedOut = current?.timedOut ?? launched.record.timedOut;
46
+ return {
47
+ title: request.description,
48
+ metadata: {
49
+ output,
50
+ description: request.description,
51
+ exit: typeof exitCode === "number" ? exitCode : null,
52
+ timedOut,
53
+ durationMs: runtimeMs,
54
+ hostSessionID: this.#host.hostSessionID,
55
+ envID,
56
+ backend: "remote",
57
+ },
58
+ output: appendRuntimeMetadata(output, runtimeMs, timedOut),
59
+ };
60
+ }
61
+ async execute(request, envID) {
62
+ this.#host.assertEnv(envID);
63
+ if (request.action === "list") {
64
+ const processes = this.#listAll();
65
+ return {
66
+ title: "Process list",
67
+ metadata: {
68
+ action: "list",
69
+ processes,
70
+ hostSessionID: this.#host.hostSessionID,
71
+ envID,
72
+ backend: "remote",
73
+ },
74
+ output: processes.length > 0 ? processes.map(renderProcessLine).join("\n") : "No running or recent processes.",
75
+ };
76
+ }
77
+ const processId = request.processId;
78
+ if (!processId) {
79
+ return this.#result({
80
+ action: request.action,
81
+ title: "Process not found",
82
+ output: "processId is required for this action",
83
+ status: "not_found",
84
+ envID,
85
+ });
86
+ }
87
+ switch (request.action) {
88
+ case "poll":
89
+ return this.#poll(processId, envID, request.block, request.timeout);
90
+ case "log":
91
+ return this.#log(processId, envID, request.offset, request.limit);
92
+ case "write":
93
+ return this.#write(processId, envID, request.data ?? "");
94
+ case "send-keys":
95
+ return this.#sendKeys(processId, envID, request.keys ?? []);
96
+ case "kill":
97
+ return this.#kill(processId, envID);
98
+ case "clear":
99
+ return this.#clear(processId, envID);
100
+ case "remove":
101
+ return this.#remove(processId, envID);
102
+ }
103
+ }
104
+ reset() {
105
+ for (const record of this.#running.values()) {
106
+ void Platform.killTree(record.child, () => record.exited);
107
+ }
108
+ this.#running.clear();
109
+ this.#finished.clear();
110
+ this.#waiters.clear();
111
+ if (this.#sweeper) {
112
+ clearInterval(this.#sweeper);
113
+ this.#sweeper = undefined;
114
+ }
115
+ }
116
+ #launchShellProcess(input) {
117
+ const launch = Platform.resolveShellLaunch(input.command);
118
+ const options = {
119
+ cwd: input.workdir,
120
+ env: Platform.normalizeEnv({ ...process.env }),
121
+ stdio: ["pipe", "pipe", "pipe"],
122
+ detached: process.platform !== "win32",
123
+ windowsHide: true,
124
+ };
125
+ const child = spawn(launch.file, launch.args, options);
126
+ const processId = crypto.randomUUID();
127
+ const record = {
128
+ processId,
129
+ command: input.command,
130
+ description: input.description,
131
+ cwd: input.workdir,
132
+ child,
133
+ stdin: child.stdin || undefined,
134
+ startedAt: Date.now(),
135
+ output: "",
136
+ tail: "",
137
+ truncated: false,
138
+ exited: false,
139
+ backgrounded: false,
140
+ timedOut: false,
141
+ };
142
+ const append = (chunk) => {
143
+ if (chunk == null)
144
+ return;
145
+ const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
146
+ const next = record.output + text;
147
+ if (next.length > MAX_OUTPUT_CHARS) {
148
+ record.output = next.slice(next.length - MAX_OUTPUT_CHARS);
149
+ record.truncated = true;
150
+ }
151
+ else {
152
+ record.output = next;
153
+ }
154
+ record.tail = record.output.slice(-TAIL_CHARS);
155
+ };
156
+ child.stdout?.on("data", append);
157
+ child.stderr?.on("data", append);
158
+ child.once("exit", (code, signal) => {
159
+ if (record.timeoutTimer) {
160
+ clearTimeout(record.timeoutTimer);
161
+ record.timeoutTimer = undefined;
162
+ }
163
+ this.#markExited(record, code, signal);
164
+ });
165
+ child.once("error", (error) => {
166
+ append(String(error));
167
+ if (record.timeoutTimer) {
168
+ clearTimeout(record.timeoutTimer);
169
+ record.timeoutTimer = undefined;
170
+ }
171
+ this.#markExited(record, 1, null);
172
+ });
173
+ if (input.timeoutMs && input.timeoutMs > 0) {
174
+ record.timeoutTimer = setTimeout(() => {
175
+ record.timedOut = true;
176
+ void Platform.killTree(child, () => record.exited);
177
+ }, input.timeoutMs);
178
+ unrefTimer(record.timeoutTimer);
179
+ }
180
+ this.#running.set(processId, record);
181
+ this.#startSweeper();
182
+ return { record };
183
+ }
184
+ async #poll(processId, envID, block, timeoutSeconds) {
185
+ const running = this.#running.get(processId);
186
+ const finished = this.#finished.get(processId);
187
+ if (!running && !finished) {
188
+ return this.#result({
189
+ action: "poll",
190
+ title: "Process not found",
191
+ output: `No process found for ${processId}`,
192
+ processId,
193
+ status: "not_found",
194
+ envID,
195
+ });
196
+ }
197
+ if (running && block) {
198
+ await this.#waitForExit(processId, (timeoutSeconds ?? 30) * 1000);
199
+ }
200
+ const currentRunning = this.#running.get(processId);
201
+ if (currentRunning) {
202
+ return this.#result({
203
+ action: "poll",
204
+ title: `Process ${processId}`,
205
+ output: (currentRunning.tail || "(no output yet)") + "\n\nProcess still running.",
206
+ processId,
207
+ status: "running",
208
+ command: currentRunning.command,
209
+ description: currentRunning.description,
210
+ envID,
211
+ });
212
+ }
213
+ const currentFinished = this.#finished.get(processId);
214
+ if (!currentFinished) {
215
+ return this.#result({
216
+ action: "poll",
217
+ title: "Process not found",
218
+ output: `No process found for ${processId}`,
219
+ processId,
220
+ status: "not_found",
221
+ envID,
222
+ });
223
+ }
224
+ return this.#result({
225
+ action: "poll",
226
+ title: `Process ${processId}`,
227
+ output: (currentFinished.tail || "(no output recorded)") +
228
+ `\n\nProcess exited with ${currentFinished.exitSignal ? `signal ${currentFinished.exitSignal}` : `code ${currentFinished.exitCode ?? 0}`}.`,
229
+ processId,
230
+ status: currentFinished.status,
231
+ command: currentFinished.command,
232
+ description: currentFinished.description,
233
+ exitCode: typeof currentFinished.exitCode === "number" ? currentFinished.exitCode : undefined,
234
+ envID,
235
+ });
236
+ }
237
+ async #log(processId, envID, offset = 0, limit) {
238
+ const target = this.#getCurrentOrFinished(processId);
239
+ if (!target) {
240
+ return this.#result({
241
+ action: "log",
242
+ title: "Process not found",
243
+ output: `No process found for ${processId}`,
244
+ processId,
245
+ status: "not_found",
246
+ envID,
247
+ });
248
+ }
249
+ const lines = target.output.split("\n");
250
+ const count = limit ?? lines.length;
251
+ return this.#result({
252
+ action: "log",
253
+ title: `Log: ${processId}`,
254
+ output: lines.slice(offset, offset + count).join("\n") || "(no output)",
255
+ processId,
256
+ status: target.status,
257
+ command: target.command,
258
+ description: target.description,
259
+ nextOffset: Math.min(offset + count, lines.length),
260
+ envID,
261
+ });
262
+ }
263
+ async #write(processId, envID, data) {
264
+ const record = this.#running.get(processId);
265
+ if (!record) {
266
+ return this.#result({
267
+ action: "write",
268
+ title: "Process not found",
269
+ output: `No active process found for ${processId}`,
270
+ processId,
271
+ status: "not_found",
272
+ envID,
273
+ });
274
+ }
275
+ if (!record.backgrounded) {
276
+ return this.#result({
277
+ action: "write",
278
+ title: "Process not backgrounded",
279
+ output: `Process ${processId} is not a background process.`,
280
+ processId,
281
+ status: "error",
282
+ command: record.command,
283
+ description: record.description,
284
+ envID,
285
+ });
286
+ }
287
+ const stdin = record.stdin;
288
+ if (!stdin || stdin.destroyed) {
289
+ return this.#result({
290
+ action: "write",
291
+ title: "Stdin not writable",
292
+ output: `Process ${processId} stdin is not writable.`,
293
+ processId,
294
+ status: "error",
295
+ command: record.command,
296
+ description: record.description,
297
+ envID,
298
+ });
299
+ }
300
+ await new Promise((resolve, reject) => {
301
+ stdin.write(data, (error) => {
302
+ if (error)
303
+ reject(error);
304
+ else
305
+ resolve();
306
+ });
307
+ });
308
+ return this.#result({
309
+ action: "write",
310
+ title: `Wrote to ${processId}`,
311
+ output: `Wrote ${data.length} bytes to process ${processId}.`,
312
+ processId,
313
+ status: "running",
314
+ command: record.command,
315
+ description: record.description,
316
+ envID,
317
+ });
318
+ }
319
+ async #sendKeys(processId, envID, keys) {
320
+ if (keys.length === 0) {
321
+ return this.#result({
322
+ action: "send-keys",
323
+ title: "No keys provided",
324
+ output: "No key tokens provided for send-keys.",
325
+ processId,
326
+ status: "error",
327
+ envID,
328
+ });
329
+ }
330
+ const encoded = Platform.encodeKeySequence(keys);
331
+ const result = await this.#write(processId, envID, encoded.data);
332
+ return {
333
+ title: `Sent keys to ${processId}`,
334
+ metadata: {
335
+ ...result.metadata,
336
+ action: "send-keys",
337
+ },
338
+ output: `Sent ${encoded.data.length} bytes to process ${processId}.` +
339
+ (encoded.warnings.length > 0 ? `\nWarnings: ${encoded.warnings.join(", ")}` : ""),
340
+ };
341
+ }
342
+ async #kill(processId, envID) {
343
+ const record = this.#running.get(processId);
344
+ if (!record) {
345
+ return this.#result({
346
+ action: "kill",
347
+ title: "Process not found",
348
+ output: `No active process found for ${processId}`,
349
+ processId,
350
+ status: "not_found",
351
+ envID,
352
+ });
353
+ }
354
+ await Platform.killTree(record.child, () => record.exited);
355
+ return this.#result({
356
+ action: "kill",
357
+ title: `Killed ${processId}`,
358
+ output: `Killed process ${processId}.`,
359
+ processId,
360
+ status: "killed",
361
+ command: record.command,
362
+ description: record.description,
363
+ envID,
364
+ });
365
+ }
366
+ async #clear(processId, envID) {
367
+ const finished = this.#finished.get(processId);
368
+ if (!finished) {
369
+ if (this.#running.has(processId)) {
370
+ return this.#result({
371
+ action: "clear",
372
+ title: "Process still running",
373
+ output: `Process ${processId} is still running. Use kill or remove instead.`,
374
+ processId,
375
+ status: "error",
376
+ envID,
377
+ });
378
+ }
379
+ return this.#result({
380
+ action: "clear",
381
+ title: "Process not found",
382
+ output: `No finished process found for ${processId}`,
383
+ processId,
384
+ status: "not_found",
385
+ envID,
386
+ });
387
+ }
388
+ this.#finished.delete(processId);
389
+ return this.#result({
390
+ action: "clear",
391
+ title: `Cleared ${processId}`,
392
+ output: `Cleared process ${processId} from history.`,
393
+ processId,
394
+ status: "cleared",
395
+ command: finished.command,
396
+ description: finished.description,
397
+ envID,
398
+ });
399
+ }
400
+ async #remove(processId, envID) {
401
+ const running = this.#running.get(processId);
402
+ const finished = this.#finished.get(processId);
403
+ if (running) {
404
+ await Platform.killTree(running.child, () => running.exited);
405
+ this.#running.delete(processId);
406
+ }
407
+ this.#finished.delete(processId);
408
+ return this.#result({
409
+ action: "remove",
410
+ title: `Removed ${processId}`,
411
+ output: `Removed process ${processId}.`,
412
+ processId,
413
+ status: "removed",
414
+ command: running?.command || finished?.command,
415
+ description: running?.description || finished?.description,
416
+ envID,
417
+ });
418
+ }
419
+ #result(input) {
420
+ return {
421
+ title: input.title,
422
+ metadata: {
423
+ action: input.action,
424
+ processId: input.processId,
425
+ status: input.status,
426
+ exitCode: input.exitCode,
427
+ command: input.command,
428
+ description: input.description,
429
+ nextOffset: input.nextOffset,
430
+ hostSessionID: this.#host.hostSessionID,
431
+ envID: input.envID,
432
+ backend: "remote",
433
+ processes: input.processes,
434
+ },
435
+ output: input.output,
436
+ };
437
+ }
438
+ #backgroundResult(record, envID, description, mode, yieldMs) {
439
+ const prefix = mode === "Auto-Background" ? `Command auto-backgrounded after ${yieldMs}ms.` : "Command started in background.";
440
+ return {
441
+ title: `[${mode}] ${description}`,
442
+ metadata: {
443
+ output: record.tail,
444
+ description,
445
+ processId: record.processId,
446
+ background: true,
447
+ durationMs: this.#runtimeMs(record),
448
+ hostSessionID: this.#host.hostSessionID,
449
+ envID,
450
+ backend: "remote",
451
+ },
452
+ output: `${prefix}\n\n` +
453
+ `Process ID: ${record.processId}\n` +
454
+ `Command: ${record.command}\n` +
455
+ `Status: running\n\n` +
456
+ `Recent output:\n${record.tail || "(no output yet)"}\n\n` +
457
+ `Use process(action: \"poll\", processId: \"${record.processId}\", envID: \"${envID}\") to check status.\n` +
458
+ `Use process(action: \"log\", processId: \"${record.processId}\", envID: \"${envID}\") to get full output.\n` +
459
+ `Use process(action: \"kill\", processId: \"${record.processId}\", envID: \"${envID}\") to terminate.`,
460
+ };
461
+ }
462
+ #markExited(record, exitCode, exitSignal) {
463
+ if (record.exited)
464
+ return;
465
+ record.exited = true;
466
+ record.exitCode = exitCode;
467
+ record.exitSignal = exitSignal;
468
+ record.tail = record.output.slice(-TAIL_CHARS);
469
+ this.#running.delete(record.processId);
470
+ if (record.backgrounded) {
471
+ this.#finished.set(record.processId, {
472
+ processId: record.processId,
473
+ command: record.command,
474
+ description: record.description,
475
+ cwd: record.cwd,
476
+ status: classifyExit(exitCode, exitSignal),
477
+ startedAt: record.startedAt,
478
+ endedAt: Date.now(),
479
+ output: record.output,
480
+ tail: record.tail,
481
+ truncated: record.truncated,
482
+ exitCode,
483
+ exitSignal,
484
+ });
485
+ }
486
+ const waiters = this.#waiters.get(record.processId);
487
+ if (waiters) {
488
+ this.#waiters.delete(record.processId);
489
+ for (const resolve of waiters)
490
+ resolve();
491
+ }
492
+ }
493
+ #waitForExit(processId, timeoutMs) {
494
+ const running = this.#running.get(processId);
495
+ if (!running || running.exited)
496
+ return Promise.resolve();
497
+ return new Promise((resolve) => {
498
+ const waiters = this.#waiters.get(processId) || new Set();
499
+ const done = () => resolve();
500
+ waiters.add(done);
501
+ this.#waiters.set(processId, waiters);
502
+ if (timeoutMs && timeoutMs > 0) {
503
+ const timer = setTimeout(() => {
504
+ waiters.delete(done);
505
+ resolve();
506
+ }, timeoutMs);
507
+ unrefTimer(timer);
508
+ }
509
+ });
510
+ }
511
+ #getCurrentOrFinished(processId) {
512
+ const running = this.#running.get(processId);
513
+ if (running) {
514
+ return {
515
+ processId: running.processId,
516
+ command: running.command,
517
+ description: running.description,
518
+ output: running.output,
519
+ tail: running.tail,
520
+ exitCode: running.exitCode,
521
+ exitSignal: running.exitSignal,
522
+ status: "running",
523
+ startedAt: running.startedAt,
524
+ timedOut: running.timedOut,
525
+ };
526
+ }
527
+ const finished = this.#finished.get(processId);
528
+ if (!finished)
529
+ return undefined;
530
+ return finished;
531
+ }
532
+ #listAll() {
533
+ const running = [...this.#running.values()]
534
+ .filter((record) => record.backgrounded)
535
+ .map((record) => ({
536
+ processId: record.processId,
537
+ status: "running",
538
+ command: trimCommand(record.command),
539
+ description: record.description,
540
+ runtimeMs: this.#runtimeMs(record),
541
+ }));
542
+ const finished = [...this.#finished.values()].map((record) => ({
543
+ processId: record.processId,
544
+ status: record.status,
545
+ command: trimCommand(record.command),
546
+ description: record.description,
547
+ runtimeMs: record.endedAt - record.startedAt,
548
+ }));
549
+ return [...running, ...finished].sort((left, right) => right.runtimeMs - left.runtimeMs);
550
+ }
551
+ #runtimeMs(record) {
552
+ return (record.endedAt ?? Date.now()) - record.startedAt;
553
+ }
554
+ #startSweeper() {
555
+ if (this.#sweeper)
556
+ return;
557
+ this.#sweeper = setInterval(() => {
558
+ const cutoff = Date.now() - this.#ttlMs;
559
+ for (const [processId, record] of this.#finished.entries()) {
560
+ if (record.endedAt < cutoff)
561
+ this.#finished.delete(processId);
562
+ }
563
+ }, Math.max(30_000, Math.floor(this.#ttlMs / 6)));
564
+ unrefTimer(this.#sweeper);
565
+ }
566
+ }
567
+ function appendRuntimeMetadata(output, durationMs, timedOut) {
568
+ const lines = [`durationMs=${durationMs}`];
569
+ if (timedOut)
570
+ lines.push("timedOut=true");
571
+ return output + `\n\n<meta_runtime>\n${lines.join("\n")}\n</meta_runtime>`;
572
+ }
573
+ function classifyExit(exitCode, exitSignal) {
574
+ if (exitSignal === "SIGKILL" || exitSignal === "SIGTERM")
575
+ return "killed";
576
+ return (exitCode ?? 0) === 0 ? "completed" : "failed";
577
+ }
578
+ function trimCommand(command) {
579
+ return command.length > 80 ? `${command.slice(0, 77)}...` : command;
580
+ }
581
+ function renderProcessLine(processInfo) {
582
+ const label = processInfo.description || processInfo.command;
583
+ return `${processInfo.processId} ${processInfo.status.padEnd(9)} ${formatDuration(processInfo.runtimeMs)} :: ${label}`;
584
+ }
585
+ function formatDuration(ms) {
586
+ const seconds = Math.floor(ms / 1000);
587
+ if (seconds < 60)
588
+ return `${seconds}s`.padStart(6);
589
+ const minutes = Math.floor(seconds / 60);
590
+ const remainder = seconds % 60;
591
+ return `${minutes}m${remainder}s`.padStart(6);
592
+ }
593
+ function unrefTimer(timer) {
594
+ if (typeof timer === "object" && timer && "unref" in timer && typeof timer.unref === "function") {
595
+ timer.unref();
596
+ }
597
+ }
@@ -0,0 +1,32 @@
1
+ import { MetaProtocolEnv, MetaProtocolHost } from "@ericsanchezok/meta-protocol";
2
+ export interface MetaSynergyHostOptions {
3
+ envID?: MetaProtocolEnv.EnvID;
4
+ hostSessionID?: MetaProtocolEnv.HostSessionID;
5
+ capabilities?: MetaProtocolHost.Capabilities;
6
+ }
7
+ export declare class MetaSynergyHost {
8
+ readonly envID?: MetaProtocolEnv.EnvID;
9
+ readonly hostSessionID: MetaProtocolEnv.HostSessionID;
10
+ readonly capabilities: MetaProtocolHost.Capabilities;
11
+ constructor(options?: MetaSynergyHostOptions);
12
+ hello(): {
13
+ type: "host.hello";
14
+ envID: string;
15
+ hostSessionID: string;
16
+ capabilities: {
17
+ platform: string;
18
+ arch: string;
19
+ runtime: "unknown" | "node" | "bun";
20
+ defaultShell: "none" | "sh" | "cmd" | "powershell" | "pwsh";
21
+ supportedShells: ("none" | "sh" | "cmd" | "powershell" | "pwsh")[];
22
+ supportsPty: boolean;
23
+ supportsSendKeys: boolean;
24
+ supportsSoftKill: boolean;
25
+ supportsProcessGroups: boolean;
26
+ envCaseInsensitive: boolean;
27
+ lineEndings: "lf" | "crlf";
28
+ hostname?: string | undefined;
29
+ };
30
+ };
31
+ assertEnv(envID: string): void;
32
+ }
@@ -0,0 +1,27 @@
1
+ import { Platform } from "./platform";
2
+ export class MetaSynergyHost {
3
+ envID;
4
+ hostSessionID;
5
+ capabilities;
6
+ constructor(options = {}) {
7
+ this.envID = options.envID;
8
+ this.hostSessionID = options.hostSessionID || crypto.randomUUID();
9
+ this.capabilities = options.capabilities || Platform.detectCapabilities();
10
+ }
11
+ hello() {
12
+ if (!this.envID) {
13
+ throw new Error("MetaSynergyHost requires envID to emit host.hello");
14
+ }
15
+ return {
16
+ type: "host.hello",
17
+ envID: this.envID,
18
+ hostSessionID: this.hostSessionID,
19
+ capabilities: this.capabilities,
20
+ };
21
+ }
22
+ assertEnv(envID) {
23
+ if (this.envID && envID !== this.envID) {
24
+ throw new Error(`env mismatch: host bound to ${this.envID}, received ${envID}`);
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,8 @@
1
+ export * from "./types";
2
+ export * from "./host";
3
+ export * from "./platform";
4
+ export * from "./client/holos-client";
5
+ export * from "./rpc/schema";
6
+ export * from "./rpc/handler";
7
+ export * from "./exec/bash-runner";
8
+ export * from "./exec/process-registry";
@@ -0,0 +1,8 @@
1
+ export * from "./types";
2
+ export * from "./host";
3
+ export * from "./platform";
4
+ export * from "./client/holos-client";
5
+ export * from "./rpc/schema";
6
+ export * from "./rpc/handler";
7
+ export * from "./exec/bash-runner";
8
+ export * from "./exec/process-registry";