omnara 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -6204,7 +6204,7 @@ var FALLBACK_NAME = pkg.name ?? "omnara";
6204
6204
  var FALLBACK_VERSION = pkg.version ?? "0.0.0";
6205
6205
  var FALLBACK_DESCRIPTION = pkg.description ?? "Command-line interface for Omnara.";
6206
6206
  var PACKAGE_NAME = true ? "omnara" : FALLBACK_NAME;
6207
- var PACKAGE_VERSION = true ? "0.3.1" : FALLBACK_VERSION;
6207
+ var PACKAGE_VERSION = true ? "0.3.2" : FALLBACK_VERSION;
6208
6208
  var PACKAGE_DESCRIPTION = true ? "Command-line interface for Omnara." : FALLBACK_DESCRIPTION;
6209
6209
 
6210
6210
  // src/auth/supabase-client.ts
@@ -8041,8 +8041,8 @@ import * as Sentry26 from "@sentry/node";
8041
8041
  // src/daemon/daemon-launcher.ts
8042
8042
  import { spawn as spawn2, exec as execCb } from "node:child_process";
8043
8043
  import path12 from "node:path";
8044
- import os3 from "node:os";
8045
- import { promisify } from "node:util";
8044
+ import os4 from "node:os";
8045
+ import { promisify as promisify2 } from "node:util";
8046
8046
 
8047
8047
  // src/daemon/daemon-client.ts
8048
8048
  import http2 from "node:http";
@@ -8494,9 +8494,14 @@ import chalk2 from "chalk";
8494
8494
  // src/daemon/session-pid-registry.ts
8495
8495
  import fs10 from "node:fs/promises";
8496
8496
  import path11 from "node:path";
8497
+ import os3 from "node:os";
8498
+ import { execFile } from "node:child_process";
8499
+ import { promisify } from "node:util";
8497
8500
  import * as Sentry20 from "@sentry/node";
8498
8501
  var REGISTRY_PATH = path11.join(OMNARA_HOME_DIR, "session-processes.json");
8499
8502
  var LOCK_PATH = `${REGISTRY_PATH}.lock`;
8503
+ var execFileAsync = promisify(execFile);
8504
+ var START_TIME_TOLERANCE_MS = 5e3;
8500
8505
  async function acquireLock(retries = 20, delayMs = 50) {
8501
8506
  for (let attempt = 0; attempt < retries; attempt += 1) {
8502
8507
  try {
@@ -8519,7 +8524,24 @@ async function releaseLock(handle) {
8519
8524
  async function readRegistry() {
8520
8525
  try {
8521
8526
  const raw = await fs10.readFile(REGISTRY_PATH, "utf8");
8522
- return JSON.parse(raw);
8527
+ const parsed = JSON.parse(raw);
8528
+ const normalized = {};
8529
+ for (const [sessionId, value] of Object.entries(parsed)) {
8530
+ if (typeof value === "number") {
8531
+ normalized[sessionId] = {
8532
+ pid: value
8533
+ };
8534
+ continue;
8535
+ }
8536
+ if (value && typeof value.pid === "number") {
8537
+ normalized[sessionId] = {
8538
+ pid: value.pid,
8539
+ startedAt: typeof value.startedAt === "string" ? value.startedAt : void 0,
8540
+ command: typeof value.command === "string" ? value.command : void 0
8541
+ };
8542
+ }
8543
+ }
8544
+ return normalized;
8523
8545
  } catch (error) {
8524
8546
  if (error.code === "ENOENT") {
8525
8547
  return {};
@@ -8556,9 +8578,69 @@ async function isProcessAlive3(pid) {
8556
8578
  return true;
8557
8579
  }
8558
8580
  }
8581
+ async function isSessionProcessAlive(record) {
8582
+ const alive = await isProcessAlive3(record.pid);
8583
+ if (!alive) {
8584
+ return false;
8585
+ }
8586
+ const tokensMatch = await matchesExpectedCommand(record.pid);
8587
+ if (!tokensMatch) {
8588
+ return false;
8589
+ }
8590
+ const timeMatch = await matchesStartTime(record.pid, record.startedAt);
8591
+ if (record.startedAt && !timeMatch) {
8592
+ return false;
8593
+ }
8594
+ return true;
8595
+ }
8596
+ async function matchesExpectedCommand(pid) {
8597
+ if (os3.platform() === "win32") {
8598
+ return true;
8599
+ }
8600
+ try {
8601
+ const { stdout } = await execFileAsync("ps", ["-p", String(pid), "-o", "command="]);
8602
+ const command = stdout.trim().toLowerCase();
8603
+ if (!command) {
8604
+ return false;
8605
+ }
8606
+ if (!command.includes("omnara") || !command.includes("daemon run-session")) {
8607
+ return false;
8608
+ }
8609
+ return true;
8610
+ } catch {
8611
+ return false;
8612
+ }
8613
+ }
8614
+ async function matchesStartTime(pid, recordedStartedAt) {
8615
+ if (os3.platform() === "win32") {
8616
+ return true;
8617
+ }
8618
+ if (!recordedStartedAt) {
8619
+ return true;
8620
+ }
8621
+ const recordedMs = Date.parse(recordedStartedAt);
8622
+ if (Number.isNaN(recordedMs)) {
8623
+ return true;
8624
+ }
8625
+ try {
8626
+ const { stdout } = await execFileAsync("ps", ["-p", String(pid), "-o", "lstart="]);
8627
+ const psStart = stdout.trim();
8628
+ if (!psStart) {
8629
+ return true;
8630
+ }
8631
+ const psDate = new Date(psStart);
8632
+ if (Number.isNaN(psDate.getTime())) {
8633
+ return true;
8634
+ }
8635
+ const delta = Math.abs(psDate.getTime() - recordedMs);
8636
+ return delta <= START_TIME_TOLERANCE_MS;
8637
+ } catch {
8638
+ return true;
8639
+ }
8640
+ }
8559
8641
 
8560
8642
  // src/daemon/daemon-launcher.ts
8561
- var exec = promisify(execCb);
8643
+ var exec = promisify2(execCb);
8562
8644
  var DEFAULT_WAIT_MS = 1e4;
8563
8645
  async function ensureDaemonForSession(options = {}) {
8564
8646
  const logger3 = options.logger ?? logger;
@@ -8598,7 +8680,7 @@ async function ensureMachineNameConfigured(configStore, logger3) {
8598
8680
  process.env.OMNARA_MACHINE_NAME = config.machine.name;
8599
8681
  return config.machine.name;
8600
8682
  }
8601
- const defaultName = os3.hostname();
8683
+ const defaultName = os4.hostname();
8602
8684
  let machineName = defaultName;
8603
8685
  if (process.stdin.isTTY && process.stdout.isTTY) {
8604
8686
  const answer = (await prompt(chalk2.blue(`Enter a name for this machine [${defaultName}]: `))).trim();
@@ -8732,20 +8814,19 @@ async function killSessionsByRegistry(options = {}) {
8732
8814
  const logger3 = options.logger ?? logger;
8733
8815
  try {
8734
8816
  await withSessionRegistry(async (registry) => {
8735
- const pids = Object.values(registry);
8736
- for (const pid of pids) {
8737
- if (!Number.isFinite(pid)) {
8817
+ for (const record of Object.values(registry)) {
8818
+ if (!record || !Number.isFinite(record.pid)) {
8738
8819
  continue;
8739
8820
  }
8740
- const alive = await isProcessAlive3(pid);
8821
+ const alive = await isSessionProcessAlive(record);
8741
8822
  if (!alive) {
8742
8823
  continue;
8743
8824
  }
8744
8825
  try {
8745
- process.kill(pid, "SIGKILL");
8746
- logger3.success(`Killed session process ${pid}`);
8826
+ process.kill(record.pid, "SIGKILL");
8827
+ logger3.success(`Killed session process ${record.pid}`);
8747
8828
  } catch (error) {
8748
- logger3.warn(`Failed to kill session ${pid}: ${extractErrorMessage12(error)}`);
8829
+ logger3.warn(`Failed to kill session ${record.pid}: ${extractErrorMessage12(error)}`);
8749
8830
  }
8750
8831
  }
8751
8832
  return [{}, void 0];
@@ -9051,7 +9132,7 @@ function extractErrorMessage13(error) {
9051
9132
 
9052
9133
  // src/daemon/machine-supervisor.ts
9053
9134
  import { randomUUID as randomUUID7 } from "node:crypto";
9054
- import os4 from "node:os";
9135
+ import os5 from "node:os";
9055
9136
  import { setTimeout as delay5 } from "node:timers/promises";
9056
9137
  import WebSocket2 from "ws";
9057
9138
  import * as Sentry23 from "@sentry/node";
@@ -9090,13 +9171,13 @@ async function ensureWorkspace(client, accessToken, payload) {
9090
9171
  // src/daemon/session-runner.ts
9091
9172
  import { spawn as spawn3 } from "node:child_process";
9092
9173
  import * as Sentry22 from "@sentry/node";
9174
+ import fs11 from "node:fs";
9093
9175
  import path13 from "node:path";
9094
9176
  var SessionManager = class {
9095
9177
  backendUrl;
9096
9178
  frontendUrl;
9097
9179
  directoryRegistry;
9098
9180
  logger;
9099
- runners = /* @__PURE__ */ new Map();
9100
9181
  constructor(options) {
9101
9182
  this.backendUrl = options.backendUrl;
9102
9183
  this.frontendUrl = options.frontendUrl;
@@ -9104,9 +9185,6 @@ var SessionManager = class {
9104
9185
  this.logger = options.logger;
9105
9186
  }
9106
9187
  async startSession(spec) {
9107
- if (this.runners.has(spec.launchId)) {
9108
- throw new Error("Session is already running on this machine");
9109
- }
9110
9188
  const entry = await this.directoryRegistry.getEntry(spec.directory);
9111
9189
  if (!entry) {
9112
9190
  throw new Error(`Directory ${spec.directory} is not registered on this machine`);
@@ -9118,23 +9196,15 @@ var SessionManager = class {
9118
9196
  frontendUrl: this.frontendUrl,
9119
9197
  logger: this.logger,
9120
9198
  onExit: () => {
9121
- this.runners.delete(spec.launchId);
9122
9199
  }
9123
9200
  });
9124
- this.runners.set(spec.launchId, runner);
9125
9201
  try {
9126
9202
  await runner.start();
9127
9203
  } catch (error) {
9128
- this.runners.delete(spec.launchId);
9129
9204
  Sentry22.captureException(error);
9130
9205
  throw error;
9131
9206
  }
9132
9207
  }
9133
- async stopAll() {
9134
- const runners = Array.from(this.runners.values());
9135
- this.runners.clear();
9136
- await Promise.all(runners.map((runner) => runner.stop()));
9137
- }
9138
9208
  };
9139
9209
  var SessionRunner = class {
9140
9210
  spec;
@@ -9143,12 +9213,7 @@ var SessionRunner = class {
9143
9213
  frontendUrl;
9144
9214
  logger;
9145
9215
  onExit;
9146
- startedAt = null;
9147
9216
  child = null;
9148
- exitPromise = null;
9149
- resolveExit = null;
9150
- exitHandled = false;
9151
- sessionLogPath = null;
9152
9217
  constructor(options) {
9153
9218
  this.spec = options.spec;
9154
9219
  this.directory = options.directory;
@@ -9165,15 +9230,12 @@ var SessionRunner = class {
9165
9230
  if (!entry) {
9166
9231
  throw new Error("Unable to determine CLI entry path for session runner");
9167
9232
  }
9168
- this.exitPromise = new Promise((resolve) => {
9169
- this.resolveExit = resolve;
9170
- });
9171
- this.sessionLogPath = await createSessionLogPath(this.spec.workspaceId, this.spec.launchId);
9172
- this.logger.info(`Session ${this.spec.launchId} logs available at ${this.sessionLogPath}`);
9233
+ const sessionLogPath = await createSessionLogPath(this.spec.workspaceId, this.spec.launchId);
9234
+ this.logger.info(`Session ${this.spec.launchId} logs available at ${sessionLogPath}`);
9173
9235
  await withSessionRegistry(async (registry) => {
9174
- const existingPid = registry[this.spec.userSessionId];
9175
- if (existingPid) {
9176
- const alive = await isProcessAlive3(existingPid);
9236
+ const existing = registry[this.spec.userSessionId];
9237
+ if (existing) {
9238
+ const alive = await isSessionProcessAlive(existing);
9177
9239
  if (alive) {
9178
9240
  throw new Error(`User session ${this.spec.userSessionId} is already running`);
9179
9241
  }
@@ -9199,7 +9261,7 @@ var SessionRunner = class {
9199
9261
  OMNARA_WORKSPACE_ID: this.spec.workspaceId,
9200
9262
  OMNARA_WORKSPACE_DIR: this.directory.path,
9201
9263
  OMNARA_USER_SESSION_ID: this.spec.userSessionId,
9202
- ...this.sessionLogPath ? { OMNARA_SESSION_LOG_PATH: this.sessionLogPath } : {}
9264
+ OMNARA_SESSION_LOG_PATH: sessionLogPath
9203
9265
  };
9204
9266
  if (this.spec.workspaceConfig) {
9205
9267
  env.OMNARA_WORKSPACE_CONFIG = JSON.stringify(this.spec.workspaceConfig);
@@ -9210,95 +9272,58 @@ var SessionRunner = class {
9210
9272
  if (this.spec.metadata) {
9211
9273
  env.OMNARA_WORKSPACE_METADATA = JSON.stringify(this.spec.metadata);
9212
9274
  }
9213
- const child = spawn3(process.execPath, args, {
9214
- cwd: this.directory.path,
9215
- stdio: ["ignore", "ignore", "ignore"],
9216
- env
9217
- });
9218
- this.child = child;
9219
- let spawned = false;
9220
- const spawnPromise = new Promise((resolve, reject) => {
9221
- child.once("spawn", () => {
9222
- spawned = true;
9223
- this.startedAt = (/* @__PURE__ */ new Date()).toISOString();
9224
- this.writeLog("INFO", `Session process started in ${this.directory.path}`);
9225
- resolve();
9275
+ let logFd = null;
9276
+ try {
9277
+ logFd = fs11.openSync(sessionLogPath, "a");
9278
+ const child = spawn3(process.execPath, args, {
9279
+ cwd: this.directory.path,
9280
+ stdio: ["ignore", logFd, logFd],
9281
+ env,
9282
+ detached: true
9226
9283
  });
9227
- child.once("error", (error) => {
9228
- if (!spawned) {
9229
- Sentry22.captureException(error);
9230
- this.writeLog("ERROR", error.message);
9231
- this.cleanup();
9232
- this.resolveExit?.();
9233
- reject(error);
9234
- } else {
9235
- Sentry22.captureException(error);
9236
- this.handleExit(error.message, true);
9237
- }
9284
+ fs11.closeSync(logFd);
9285
+ logFd = null;
9286
+ child.unref();
9287
+ await new Promise((resolve, reject) => {
9288
+ child.once("spawn", resolve);
9289
+ child.once("error", reject);
9238
9290
  });
9239
- child.once("exit", (code, signal) => {
9240
- const message = buildExitMessage(code, signal);
9241
- const failed = typeof code === "number" && code !== 0;
9242
- this.handleExit(message, failed);
9291
+ this.child = child;
9292
+ child.once("exit", () => {
9293
+ this.onExit();
9294
+ this.child = null;
9243
9295
  });
9244
- });
9245
- await withSessionRegistry(async (registry) => {
9246
- registry[this.spec.userSessionId] = child.pid ?? -1;
9247
- return [registry, null];
9248
- });
9249
- await spawnPromise;
9296
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
9297
+ const command = `${process.execPath} ${args.join(" ")}`;
9298
+ await withSessionRegistry(async (registry) => {
9299
+ registry[this.spec.userSessionId] = {
9300
+ pid: child.pid ?? -1,
9301
+ startedAt,
9302
+ command
9303
+ };
9304
+ return [registry, null];
9305
+ });
9306
+ } catch (error) {
9307
+ if (logFd !== null) {
9308
+ try {
9309
+ fs11.closeSync(logFd);
9310
+ } catch {
9311
+ }
9312
+ }
9313
+ Sentry22.captureException(error);
9314
+ this.logger.error("Failed to start session process:", error);
9315
+ throw error;
9316
+ }
9250
9317
  }
9251
9318
  async stop() {
9252
9319
  if (!this.child) {
9253
- await this.exitPromise;
9254
9320
  return;
9255
9321
  }
9256
9322
  if (!this.child.killed) {
9257
9323
  this.child.kill("SIGINT");
9258
9324
  }
9259
- await this.exitPromise;
9260
- }
9261
- handleExit(reason, failed) {
9262
- if (this.exitHandled) {
9263
- return;
9264
- }
9265
- this.exitHandled = true;
9266
- if (reason) {
9267
- this.writeLog(failed ? "ERROR" : "INFO", reason);
9268
- }
9269
- this.cleanup();
9270
- this.resolveExit?.();
9271
- this.onExit();
9272
- }
9273
- cleanup() {
9274
- if (this.child) {
9275
- this.child.removeAllListeners();
9276
- this.child = null;
9277
- }
9278
- void withSessionRegistry(async (registry) => {
9279
- delete registry[this.spec.userSessionId];
9280
- return [registry, null];
9281
- });
9282
- }
9283
- writeLog(level, ...args) {
9284
- if (level === "ERROR") {
9285
- this.logger.error(...args);
9286
- } else if (level === "WARN") {
9287
- this.logger.warn(...args);
9288
- } else {
9289
- this.logger.info(...args);
9290
- }
9291
9325
  }
9292
9326
  };
9293
- function buildExitMessage(code, signal) {
9294
- if (code !== null) {
9295
- return code === 0 ? null : `Process exited with code ${code}`;
9296
- }
9297
- if (signal) {
9298
- return `Process terminated via signal ${signal}`;
9299
- }
9300
- return null;
9301
- }
9302
9327
  async function createSessionLogPath(workspaceId, launchId) {
9303
9328
  await ensureOmnaraHome();
9304
9329
  const workspaceDir = path13.join(USER_SESSION_LOG_ROOT, workspaceId);
@@ -9312,6 +9337,7 @@ async function createSessionLogPath(workspaceId, launchId) {
9312
9337
  var HEARTBEAT_INTERVAL_MS = 1e4;
9313
9338
  var RECONNECT_DELAY_MS = 2e3;
9314
9339
  var LOGIN_WAIT_DELAY_MS2 = 3e4;
9340
+ var CONNECT_TIMEOUT_MS = 3e4;
9315
9341
  var DAEMON_VERSION = PACKAGE_VERSION ?? "0.0.0";
9316
9342
  var MachineSupervisor = class {
9317
9343
  backendUrl;
@@ -9376,15 +9402,14 @@ var MachineSupervisor = class {
9376
9402
  type: "heartbeat",
9377
9403
  status,
9378
9404
  metadata: {
9379
- hostname: os4.hostname(),
9380
- platform: os4.platform(),
9381
- architecture: os4.arch(),
9405
+ hostname: os5.hostname(),
9406
+ platform: os5.platform(),
9407
+ architecture: os5.arch(),
9382
9408
  daemon_version: DAEMON_VERSION
9383
9409
  },
9384
9410
  directories: directories.map((entry) => entry.path)
9385
9411
  };
9386
9412
  await this.sendMessage(payload);
9387
- await this.sessionManager.stopAll();
9388
9413
  await this.closeWebsocket();
9389
9414
  if (this.loopPromise) {
9390
9415
  await this.loopPromise;
@@ -9435,9 +9460,9 @@ var MachineSupervisor = class {
9435
9460
  const payload = {
9436
9461
  machine_id: config.machine?.id,
9437
9462
  machine_name: machineName,
9438
- hostname: os4.hostname(),
9439
- platform: os4.platform(),
9440
- architecture: os4.arch(),
9463
+ hostname: os5.hostname(),
9464
+ platform: os5.platform(),
9465
+ architecture: os5.arch(),
9441
9466
  daemon_version: DAEMON_VERSION,
9442
9467
  directories: directories.map((entry) => entry.path),
9443
9468
  metadata: {
@@ -9494,7 +9519,38 @@ var MachineSupervisor = class {
9494
9519
  }
9495
9520
  });
9496
9521
  this.websocket = ws;
9522
+ let done = false;
9523
+ let connectTimer = null;
9524
+ const finish = (error) => {
9525
+ if (done) return;
9526
+ done = true;
9527
+ if (connectTimer) {
9528
+ clearTimeout(connectTimer);
9529
+ connectTimer = null;
9530
+ }
9531
+ this.clearHeartbeatTimer();
9532
+ this.websocket = null;
9533
+ try {
9534
+ ws.removeAllListeners();
9535
+ if (ws.readyState === WebSocket2.OPEN || ws.readyState === WebSocket2.CONNECTING) {
9536
+ ws.close();
9537
+ }
9538
+ } catch {
9539
+ }
9540
+ if (error) {
9541
+ reject(error);
9542
+ } else {
9543
+ resolve();
9544
+ }
9545
+ };
9546
+ connectTimer = setTimeout(() => {
9547
+ finish(new Error("machine websocket connection timed out"));
9548
+ }, CONNECT_TIMEOUT_MS);
9497
9549
  ws.on("open", () => {
9550
+ if (connectTimer) {
9551
+ clearTimeout(connectTimer);
9552
+ connectTimer = null;
9553
+ }
9498
9554
  this.logger.success("\u2713 Machine daemon connected to backend");
9499
9555
  void this.sendHeartbeat().catch((error) => {
9500
9556
  this.logger.error("Failed to send heartbeat:", error);
@@ -9510,20 +9566,23 @@ var MachineSupervisor = class {
9510
9566
  }
9511
9567
  });
9512
9568
  ws.on("close", (code, reason) => {
9569
+ if (connectTimer) {
9570
+ clearTimeout(connectTimer);
9571
+ connectTimer = null;
9572
+ }
9513
9573
  this.logger.warn(
9514
9574
  `Machine websocket closed (${code}${reason ? `: ${reason.toString()}` : ""})`
9515
9575
  );
9516
- this.clearHeartbeatTimer();
9517
- this.websocket = null;
9518
9576
  if (this.stopRequested) {
9519
- resolve();
9577
+ finish();
9520
9578
  } else {
9521
- reject(new Error("machine websocket closed"));
9579
+ finish(new Error("machine websocket closed"));
9522
9580
  }
9523
9581
  });
9524
9582
  ws.on("error", (error) => {
9525
9583
  Sentry23.captureException(error);
9526
9584
  this.logger.error("Machine websocket error:", error);
9585
+ finish(new Error("machine websocket error"));
9527
9586
  });
9528
9587
  });
9529
9588
  }
@@ -9711,9 +9770,9 @@ var MachineSupervisor = class {
9711
9770
  type: "heartbeat",
9712
9771
  status: "ONLINE",
9713
9772
  metadata: {
9714
- hostname: os4.hostname(),
9715
- platform: os4.platform(),
9716
- architecture: os4.arch(),
9773
+ hostname: os5.hostname(),
9774
+ platform: os5.platform(),
9775
+ architecture: os5.arch(),
9717
9776
  daemon_version: DAEMON_VERSION
9718
9777
  },
9719
9778
  directories: directories.map((entry) => entry.path)
@@ -9782,7 +9841,7 @@ function isUnauthorizedApiError2(error) {
9782
9841
  return error instanceof ApiError && (error.status === 401 || error.status === 403);
9783
9842
  }
9784
9843
  function deriveMachineName() {
9785
- return process.env.OMNARA_MACHINE_NAME || os4.hostname();
9844
+ return process.env.OMNARA_MACHINE_NAME || os5.hostname();
9786
9845
  }
9787
9846
  function decodeJson(raw) {
9788
9847
  const text = typeof raw === "string" ? raw : raw.toString("utf8");
@@ -9902,7 +9961,7 @@ var DaemonService = class {
9902
9961
  };
9903
9962
 
9904
9963
  // src/daemon/run-daemon.ts
9905
- import fs11 from "node:fs";
9964
+ import fs12 from "node:fs";
9906
9965
  import path14 from "node:path";
9907
9966
  async function runDaemonProcess() {
9908
9967
  await ensureOmnaraHome();
@@ -9910,7 +9969,7 @@ async function runDaemonProcess() {
9910
9969
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
9911
9970
  const fileName = `${timestamp}-${process.pid}.log`;
9912
9971
  const logPath = path14.join(DAEMON_LOG_DIR, fileName);
9913
- const stream = fs11.createWriteStream(logPath, {
9972
+ const stream = fs12.createWriteStream(logPath, {
9914
9973
  flags: "a",
9915
9974
  mode: DEFAULT_FILE_MODE2
9916
9975
  });