omnara 0.5.0 → 0.5.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
@@ -158,6 +158,7 @@ import { simpleGit as simpleGit2 } from "simple-git";
158
158
  import {
159
159
  query as query2
160
160
  } from "@anthropic-ai/claude-agent-sdk";
161
+ import { setTimeout as delay } from "node:timers/promises";
161
162
 
162
163
  // src/utils/logger.ts
163
164
  import path from "path";
@@ -442,6 +443,16 @@ function extractErrorMessage(error) {
442
443
  return String(error);
443
444
  }
444
445
 
446
+ // src/utils/backoff.ts
447
+ var INITIAL_RETRY_DELAY_MS = 1e3;
448
+ var MAX_RETRY_DELAY_MS = 3e4;
449
+ function calculateBackoff(currentDelayMs) {
450
+ let nextDelay = currentDelayMs * 2;
451
+ const jitterRange = nextDelay * 0.25;
452
+ nextDelay = nextDelay - jitterRange + Math.random() * jitterRange * 2;
453
+ return Math.min(nextDelay, MAX_RETRY_DELAY_MS);
454
+ }
455
+
445
456
  // src/orchestrator/utils/envelope-utils.ts
446
457
  import { randomUUID } from "crypto";
447
458
  function createEnvelope(sessionId, content, senderId, metadata, payloadMetadata) {
@@ -629,6 +640,7 @@ function buildAgentCompletionMessage(resultText, summary, errorText) {
629
640
  }
630
641
 
631
642
  // src/orchestrator/session.ts
643
+ var MAX_GENERATION_RETRIES = 6;
632
644
  var Session = class {
633
645
  constructor(id, ai, options = {}) {
634
646
  this.id = id;
@@ -653,6 +665,8 @@ var Session = class {
653
665
  announced = false;
654
666
  shouldSummarize = false;
655
667
  conversationHistory = [];
668
+ retryDelayMs = INITIAL_RETRY_DELAY_MS;
669
+ retryCount = 0;
656
670
  sessionInitControl;
657
671
  enqueue(message) {
658
672
  this.queue.push(message);
@@ -727,6 +741,7 @@ var Session = class {
727
741
  await this.dispatchEnvelope(envelope);
728
742
  }
729
743
  }
744
+ this.resetRetryState();
730
745
  const complete = await this.buildCompletionEnvelope(resultMessage, summaryTextBlocks);
731
746
  await Promise.all([
732
747
  this.emitProgress("idle", "Ready for next message"),
@@ -738,14 +753,42 @@ var Session = class {
738
753
  sessionId: this.id,
739
754
  pendingMessages: this.queue.length
740
755
  });
741
- if (this.queue.length > 0) {
742
- this.startGenerate();
743
- }
744
756
  }
745
757
  }
746
758
  startGenerate() {
747
- void this.generate().catch((error) => {
748
- logger.error("[Session] Generation failed", { sessionId: this.id, error });
759
+ void this.generate().then(() => {
760
+ if (this.queue.length > 0) {
761
+ this.startGenerate();
762
+ }
763
+ }).catch(async (error) => {
764
+ this.retryCount++;
765
+ logger.error("[Session] Generation failed", {
766
+ sessionId: this.id,
767
+ error,
768
+ attempt: this.retryCount,
769
+ maxRetries: MAX_GENERATION_RETRIES
770
+ });
771
+ if (this.retryCount < MAX_GENERATION_RETRIES && this.queue.length > 0) {
772
+ logger.info(
773
+ `[Session] Retrying in ${Math.round(this.retryDelayMs / 1e3)}s (attempt ${this.retryCount}/${MAX_GENERATION_RETRIES})`,
774
+ { sessionId: this.id }
775
+ );
776
+ await delay(this.retryDelayMs);
777
+ this.retryDelayMs = calculateBackoff(this.retryDelayMs);
778
+ this.startGenerate();
779
+ } else if (this.queue.length > 0) {
780
+ logger.error(
781
+ `[Session] Max retries (${MAX_GENERATION_RETRIES}) reached, sending error to client`,
782
+ { sessionId: this.id }
783
+ );
784
+ const errorEnvelope = this.buildErrorCompletionEnvelope(error);
785
+ this.queue = [];
786
+ await Promise.all([
787
+ this.emitProgress("error", "Generation failed"),
788
+ this.dispatchEnvelope(errorEnvelope)
789
+ ]);
790
+ this.resetRetryState();
791
+ }
749
792
  });
750
793
  }
751
794
  captureSdkSessionId(message) {
@@ -795,6 +838,26 @@ var Session = class {
795
838
  };
796
839
  return createEnvelope(this.id, content, this.id);
797
840
  }
841
+ buildErrorCompletionEnvelope(error) {
842
+ const errorMessage = error instanceof Error ? error.message : String(error);
843
+ const content = {
844
+ type: "agent_complete",
845
+ success: false,
846
+ duration_ms: null,
847
+ cost_usd: null,
848
+ message: `Generation failed after ${this.retryCount} retries: ${errorMessage}`,
849
+ metadata: {
850
+ agent_session_id: this.id,
851
+ error: errorMessage,
852
+ retries: this.retryCount
853
+ }
854
+ };
855
+ return createEnvelope(this.id, content, this.id);
856
+ }
857
+ resetRetryState() {
858
+ this.retryDelayMs = INITIAL_RETRY_DELAY_MS;
859
+ this.retryCount = 0;
860
+ }
798
861
  async summarize(summaryTextBlocks) {
799
862
  if (summaryTextBlocks.length === 0) {
800
863
  return "No assistant output to summarize.";
@@ -1206,7 +1269,7 @@ import * as Sentry3 from "@sentry/node";
1206
1269
 
1207
1270
  // src/auth/credentials-store.ts
1208
1271
  import fs2 from "fs/promises";
1209
- import { setTimeout as delay } from "node:timers/promises";
1272
+ import { setTimeout as delay2 } from "node:timers/promises";
1210
1273
  import os2 from "node:os";
1211
1274
  import path3 from "node:path";
1212
1275
  import * as Sentry2 from "@sentry/node";
@@ -1349,7 +1412,7 @@ var CredentialsStore = class {
1349
1412
  `Timed out acquiring credentials lock ${lockPath}${formatLockHolder(holder)}`
1350
1413
  );
1351
1414
  }
1352
- await delay(LOCK_RETRY_INTERVAL_MS);
1415
+ await delay2(LOCK_RETRY_INTERVAL_MS);
1353
1416
  }
1354
1417
  }
1355
1418
  }
@@ -2398,9 +2461,11 @@ var AgentOrchestrator = class {
2398
2461
  this.started = false;
2399
2462
  }
2400
2463
  async cancel() {
2401
- await Promise.all(
2402
- this.workerManager.listWorkers().filter((worker) => worker.status === "running").map((worker) => this.workerManager.cancelWorker(worker.session.id).catch(() => false))
2403
- );
2464
+ if (this.sessionSettings.type === "orchestrator") {
2465
+ await Promise.all(
2466
+ this.workerManager.listWorkers().filter((worker) => worker.status === "running").map((worker) => this.workerManager.cancelWorker(worker.session.id).catch(() => false))
2467
+ );
2468
+ }
2404
2469
  await this.session?.cancel();
2405
2470
  }
2406
2471
  async getCompletions(request) {
@@ -7717,12 +7782,12 @@ var RelayBridge = class extends EventEmitter {
7717
7782
  }
7718
7783
  });
7719
7784
  this.ensureAgentSessionExists();
7720
- let backoffMs = 1e3;
7785
+ let backoffMs = INITIAL_RETRY_DELAY_MS;
7721
7786
  while (!this.stopped) {
7722
7787
  try {
7723
7788
  await this.establishConnection();
7724
7789
  this.clearLoginWait();
7725
- backoffMs = 1e3;
7790
+ backoffMs = INITIAL_RETRY_DELAY_MS;
7726
7791
  } catch (error) {
7727
7792
  if (isLoginRequiredError(error) || isUnauthorizedApiError(error)) {
7728
7793
  if (!this.waitingForLogin) {
@@ -7733,7 +7798,7 @@ var RelayBridge = class extends EventEmitter {
7733
7798
  } else {
7734
7799
  this.logger.debug("Relay bridge still waiting for credentials...");
7735
7800
  }
7736
- await delay2(LOGIN_WAIT_DELAY_MS);
7801
+ await delay3(LOGIN_WAIT_DELAY_MS);
7737
7802
  continue;
7738
7803
  }
7739
7804
  const aborted = isAbortError(error);
@@ -7747,8 +7812,8 @@ var RelayBridge = class extends EventEmitter {
7747
7812
  this.logger.error(
7748
7813
  `Relay connection failed: ${extractErrorMessage5(error)}. Retrying in ${Math.round(backoffMs / 1e3)}s.`
7749
7814
  );
7750
- await delay2(backoffMs);
7751
- backoffMs = Math.min(backoffMs * 2, 3e4);
7815
+ await delay3(backoffMs);
7816
+ backoffMs = calculateBackoff(backoffMs);
7752
7817
  }
7753
7818
  }
7754
7819
  }
@@ -8395,7 +8460,7 @@ function buildRelayWebsocketUrl(baseUrl, path16) {
8395
8460
  }
8396
8461
  return url.toString();
8397
8462
  }
8398
- function delay2(ms) {
8463
+ function delay3(ms) {
8399
8464
  return new Promise((resolve) => setTimeout(resolve, ms));
8400
8465
  }
8401
8466
  function extractErrorMessage5(error) {
@@ -8415,22 +8480,41 @@ function isAbortError(error) {
8415
8480
  const message = error.message ? error.message.toLowerCase() : "";
8416
8481
  return message.includes("aborted with ctrl+c");
8417
8482
  }
8483
+ var CONNECT_TIMEOUT_MS = 3e4;
8418
8484
  function waitForOpen(ws) {
8419
8485
  return new Promise((resolve, reject) => {
8420
- const onOpen = () => {
8421
- cleanup();
8422
- resolve();
8423
- };
8424
- const onError = (error) => {
8425
- cleanup();
8426
- reject(error);
8427
- };
8486
+ let timer = null;
8428
8487
  const cleanup = () => {
8488
+ if (timer) {
8489
+ clearTimeout(timer);
8490
+ timer = null;
8491
+ }
8429
8492
  ws.removeListener("open", onOpen);
8430
8493
  ws.removeListener("error", onError);
8431
8494
  };
8495
+ const settle = (error) => {
8496
+ cleanup();
8497
+ if (error) {
8498
+ try {
8499
+ if (ws.readyState === WebSocket.CONNECTING) {
8500
+ ws.terminate();
8501
+ } else if (ws.readyState === WebSocket.OPEN) {
8502
+ ws.close();
8503
+ }
8504
+ } catch {
8505
+ }
8506
+ reject(error);
8507
+ } else {
8508
+ resolve();
8509
+ }
8510
+ };
8511
+ const onOpen = () => settle();
8512
+ const onError = (error) => settle(error);
8432
8513
  ws.once("open", onOpen);
8433
8514
  ws.once("error", onError);
8515
+ timer = setTimeout(() => {
8516
+ settle(new Error("WebSocket connection timed out"));
8517
+ }, CONNECT_TIMEOUT_MS);
8434
8518
  });
8435
8519
  }
8436
8520
  function isLoginRequiredError(error) {
@@ -8525,63 +8609,7 @@ function buildOrchestratorLogger() {
8525
8609
 
8526
8610
  // src/commands/auth.ts
8527
8611
  import { Command } from "commander";
8528
- import * as Sentry13 from "@sentry/node";
8529
- function buildAuthCommand() {
8530
- const auth = new Command("auth").description("Authenticate the Omnara CLI.");
8531
- auth.command("login").description("Authenticate via the browser-based verification flow.").action(async () => {
8532
- const store = new CredentialsStore();
8533
- const backendUrl = resolveBackendUrl();
8534
- try {
8535
- await performVerificationLogin({
8536
- backendUrlOption: backendUrl,
8537
- frontendUrlOption: resolveFrontendUrl(),
8538
- store
8539
- });
8540
- } catch (error) {
8541
- Sentry13.captureException(error);
8542
- if (error instanceof ApiError) {
8543
- console.error(`
8544
- Request failed (HTTP ${error.status}): ${error.message}`);
8545
- } else {
8546
- console.error(`
8547
- Login failed: ${extractErrorMessage6(error)}`);
8548
- }
8549
- process.exitCode = 1;
8550
- }
8551
- });
8552
- auth.command("logout").description("Remove stored CLI credentials.").action(async () => {
8553
- const store = new CredentialsStore();
8554
- await store.clear();
8555
- console.log("Credentials cleared.");
8556
- });
8557
- auth.command("status").description("Show stored CLI credential details.").action(async () => {
8558
- const store = new CredentialsStore();
8559
- const credentials = await store.load();
8560
- if (!credentials) {
8561
- console.log("No credentials stored.");
8562
- return;
8563
- }
8564
- printStatus(credentials);
8565
- });
8566
- return auth;
8567
- }
8568
- function printStatus(credentials) {
8569
- if (credentials.pat) {
8570
- console.log("Authentication: PAT stored");
8571
- return;
8572
- }
8573
- console.log("Authentication: Not configured");
8574
- }
8575
- function extractErrorMessage6(error) {
8576
- if (error instanceof Error) {
8577
- return error.message;
8578
- }
8579
- return String(error);
8580
- }
8581
-
8582
- // src/commands/daemon.ts
8583
- import { Command as Command2 } from "commander";
8584
- import * as Sentry24 from "@sentry/node";
8612
+ import * as Sentry18 from "@sentry/node";
8585
8613
 
8586
8614
  // src/daemon/daemon-launcher.ts
8587
8615
  import { spawn as spawn2, exec as execCb } from "node:child_process";
@@ -8591,12 +8619,12 @@ import { promisify as promisify2 } from "node:util";
8591
8619
 
8592
8620
  // src/daemon/daemon-client.ts
8593
8621
  import http2 from "node:http";
8594
- import * as Sentry15 from "@sentry/node";
8622
+ import * as Sentry14 from "@sentry/node";
8595
8623
 
8596
8624
  // src/daemon/state-store.ts
8597
8625
  import fs7 from "node:fs/promises";
8598
8626
  import path8 from "node:path";
8599
- import * as Sentry14 from "@sentry/node";
8627
+ import * as Sentry13 from "@sentry/node";
8600
8628
 
8601
8629
  // src/daemon/paths.ts
8602
8630
  import fs6 from "node:fs/promises";
@@ -8640,9 +8668,9 @@ var DaemonStateStore = class {
8640
8668
  if (isFileNotFound2(error)) {
8641
8669
  return null;
8642
8670
  }
8643
- Sentry14.captureException(error);
8671
+ Sentry13.captureException(error);
8644
8672
  throw new Error(
8645
- `Failed to read daemon state from ${this.filePath}: ${extractErrorMessage7(error)}`
8673
+ `Failed to read daemon state from ${this.filePath}: ${extractErrorMessage6(error)}`
8646
8674
  );
8647
8675
  }
8648
8676
  }
@@ -8661,7 +8689,7 @@ var DaemonStateStore = class {
8661
8689
  await fs7.unlink(this.filePath);
8662
8690
  } catch (error) {
8663
8691
  if (!isFileNotFound2(error)) {
8664
- Sentry14.captureException(error);
8692
+ Sentry13.captureException(error);
8665
8693
  throw error;
8666
8694
  }
8667
8695
  }
@@ -8672,7 +8700,7 @@ function isFileNotFound2(error) {
8672
8700
  error && typeof error === "object" && "code" in error && error.code === "ENOENT"
8673
8701
  );
8674
8702
  }
8675
- function extractErrorMessage7(error) {
8703
+ function extractErrorMessage6(error) {
8676
8704
  if (error instanceof Error && error.message) {
8677
8705
  return error.message;
8678
8706
  }
@@ -8699,7 +8727,7 @@ var DaemonClient = class {
8699
8727
  await this.stateStore.clear();
8700
8728
  return null;
8701
8729
  }
8702
- Sentry15.captureException(error);
8730
+ Sentry14.captureException(error);
8703
8731
  throw error;
8704
8732
  }
8705
8733
  }
@@ -8719,7 +8747,7 @@ var DaemonClient = class {
8719
8747
  await this.stateStore.clear();
8720
8748
  return null;
8721
8749
  }
8722
- Sentry15.captureException(error);
8750
+ Sentry14.captureException(error);
8723
8751
  throw error;
8724
8752
  }
8725
8753
  }
@@ -8742,7 +8770,7 @@ var DaemonClient = class {
8742
8770
  await this.stateStore.clear();
8743
8771
  return false;
8744
8772
  }
8745
- Sentry15.captureException(error);
8773
+ Sentry14.captureException(error);
8746
8774
  throw error;
8747
8775
  }
8748
8776
  }
@@ -8767,7 +8795,7 @@ var DaemonClient = class {
8767
8795
  await this.stateStore.clear();
8768
8796
  return null;
8769
8797
  }
8770
- Sentry15.captureException(error);
8798
+ Sentry14.captureException(error);
8771
8799
  throw error;
8772
8800
  }
8773
8801
  }
@@ -8841,7 +8869,7 @@ function isProcessAlive2(pid) {
8841
8869
  if (isErrno2(error, "ESRCH")) {
8842
8870
  return false;
8843
8871
  }
8844
- Sentry15.captureException(error);
8872
+ Sentry14.captureException(error);
8845
8873
  return false;
8846
8874
  }
8847
8875
  }
@@ -8874,13 +8902,13 @@ var HttpError = class extends Error {
8874
8902
  // src/daemon/directory-registry.ts
8875
8903
  import fs9 from "node:fs/promises";
8876
8904
  import path10 from "node:path";
8877
- import * as Sentry17 from "@sentry/node";
8905
+ import * as Sentry16 from "@sentry/node";
8878
8906
 
8879
8907
  // src/daemon/config-store.ts
8880
8908
  import fs8 from "node:fs/promises";
8881
8909
  import path9 from "node:path";
8882
8910
  import { randomUUID as randomUUID6 } from "node:crypto";
8883
- import * as Sentry16 from "@sentry/node";
8911
+ import * as Sentry15 from "@sentry/node";
8884
8912
  var DEFAULT_CONFIG = {
8885
8913
  directories: [],
8886
8914
  settings: {}
@@ -8898,9 +8926,9 @@ var DaemonConfigStore = class {
8898
8926
  if (isFileNotFound3(error)) {
8899
8927
  return cloneConfig(DEFAULT_CONFIG);
8900
8928
  }
8901
- Sentry16.captureException(error);
8929
+ Sentry15.captureException(error);
8902
8930
  throw new Error(
8903
- `Failed to read daemon config from ${this.filePath}: ${extractErrorMessage8(error)}`
8931
+ `Failed to read daemon config from ${this.filePath}: ${extractErrorMessage7(error)}`
8904
8932
  );
8905
8933
  }
8906
8934
  }
@@ -8982,7 +9010,7 @@ function isFileNotFound3(error) {
8982
9010
  error && typeof error === "object" && "code" in error && error.code === "ENOENT"
8983
9011
  );
8984
9012
  }
8985
- function extractErrorMessage8(error) {
9013
+ function extractErrorMessage7(error) {
8986
9014
  if (error instanceof Error && error.message) {
8987
9015
  return error.message;
8988
9016
  }
@@ -9020,14 +9048,14 @@ var DirectoryRegistry = class {
9020
9048
  throw new Error("Path is not a directory");
9021
9049
  }
9022
9050
  } catch (error) {
9023
- Sentry17.captureException(error);
9051
+ Sentry16.captureException(error);
9024
9052
  throw new Error(
9025
- `Directory ${directoryPath} is not accessible: ${extractErrorMessage9(error)}`
9053
+ `Directory ${directoryPath} is not accessible: ${extractErrorMessage8(error)}`
9026
9054
  );
9027
9055
  }
9028
9056
  }
9029
9057
  };
9030
- function extractErrorMessage9(error) {
9058
+ function extractErrorMessage8(error) {
9031
9059
  if (error instanceof Error && error.message) {
9032
9060
  return error.message;
9033
9061
  }
@@ -9043,7 +9071,7 @@ import path11 from "node:path";
9043
9071
  import os3 from "node:os";
9044
9072
  import { execFile } from "node:child_process";
9045
9073
  import { promisify } from "node:util";
9046
- import * as Sentry18 from "@sentry/node";
9074
+ import * as Sentry17 from "@sentry/node";
9047
9075
  var REGISTRY_PATH = path11.join(OMNARA_HOME_DIR, "session-processes.json");
9048
9076
  var LOCK_PATH = `${REGISTRY_PATH}.lock`;
9049
9077
  var execFileAsync = promisify(execFile);
@@ -9055,7 +9083,7 @@ async function acquireLock(retries = 20, delayMs = 50) {
9055
9083
  return handle;
9056
9084
  } catch (error) {
9057
9085
  if (error.code !== "EEXIST") {
9058
- Sentry18.captureException(error);
9086
+ Sentry17.captureException(error);
9059
9087
  throw error;
9060
9088
  }
9061
9089
  await new Promise((resolve) => setTimeout(resolve, delayMs));
@@ -9092,7 +9120,7 @@ async function readRegistry() {
9092
9120
  if (error.code === "ENOENT") {
9093
9121
  return {};
9094
9122
  }
9095
- Sentry18.captureException(error);
9123
+ Sentry17.captureException(error);
9096
9124
  throw error;
9097
9125
  }
9098
9126
  }
@@ -9120,7 +9148,7 @@ async function isProcessAlive3(pid) {
9120
9148
  if (error.code === "ESRCH") {
9121
9149
  return false;
9122
9150
  }
9123
- Sentry18.captureException(error);
9151
+ Sentry17.captureException(error);
9124
9152
  return true;
9125
9153
  }
9126
9154
  }
@@ -9329,13 +9357,13 @@ async function killAllDaemonProcesses(options = {}) {
9329
9357
  process.kill(pid, "SIGKILL");
9330
9358
  logger3.success(`Killed process ${pid}`);
9331
9359
  } catch (error) {
9332
- logger3.warn(`Failed to kill ${pid}: ${extractErrorMessage10(error)}`);
9360
+ logger3.warn(`Failed to kill ${pid}: ${extractErrorMessage9(error)}`);
9333
9361
  }
9334
9362
  }
9335
9363
  await stateStore.clear();
9336
9364
  logger3.debug(`Cleared ${envLabel} daemon state file`);
9337
9365
  } catch (error) {
9338
- logger3.error(`Failed to kill ${envLabel} daemon processes: ${extractErrorMessage10(error)}`);
9366
+ logger3.error(`Failed to kill ${envLabel} daemon processes: ${extractErrorMessage9(error)}`);
9339
9367
  }
9340
9368
  }
9341
9369
  }
@@ -9352,7 +9380,7 @@ async function killDaemonByState(options = {}) {
9352
9380
  process.kill(state.pid, "SIGKILL");
9353
9381
  logger3.success(`Killed daemon process ${state.pid}`);
9354
9382
  } catch (error) {
9355
- logger3.warn(`Failed to kill daemon ${state.pid}: ${extractErrorMessage10(error)}`);
9383
+ logger3.warn(`Failed to kill daemon ${state.pid}: ${extractErrorMessage9(error)}`);
9356
9384
  }
9357
9385
  await stateStore.clear();
9358
9386
  }
@@ -9372,13 +9400,13 @@ async function killSessionsByRegistry(options = {}) {
9372
9400
  process.kill(record.pid, "SIGKILL");
9373
9401
  logger3.success(`Killed session process ${record.pid}`);
9374
9402
  } catch (error) {
9375
- logger3.warn(`Failed to kill session ${record.pid}: ${extractErrorMessage10(error)}`);
9403
+ logger3.warn(`Failed to kill session ${record.pid}: ${extractErrorMessage9(error)}`);
9376
9404
  }
9377
9405
  }
9378
9406
  return [{}, void 0];
9379
9407
  });
9380
9408
  } catch (error) {
9381
- logger3.warn(`Failed to kill sessions via registry: ${extractErrorMessage10(error)}`);
9409
+ logger3.warn(`Failed to kill sessions via registry: ${extractErrorMessage9(error)}`);
9382
9410
  }
9383
9411
  }
9384
9412
  async function findDaemonRelatedPids() {
@@ -9415,7 +9443,7 @@ async function registerDirectory(directoryPath, options) {
9415
9443
  options.logger.warn("Daemon was unavailable; storing directory locally.");
9416
9444
  } catch (error) {
9417
9445
  options.logger.warn(
9418
- `Failed to register ${normalized} with daemon: ${extractErrorMessage10(error)}`
9446
+ `Failed to register ${normalized} with daemon: ${extractErrorMessage9(error)}`
9419
9447
  );
9420
9448
  }
9421
9449
  const registry = new DirectoryRegistry();
@@ -9453,13 +9481,73 @@ async function waitForReady(client, options = {}) {
9453
9481
  logger3.warn("Timed out waiting for daemon readiness.");
9454
9482
  return null;
9455
9483
  }
9456
- function extractErrorMessage10(error) {
9484
+ function extractErrorMessage9(error) {
9457
9485
  if (error instanceof Error && error.message) {
9458
9486
  return error.message;
9459
9487
  }
9460
9488
  return String(error);
9461
9489
  }
9462
9490
 
9491
+ // src/commands/auth.ts
9492
+ function buildAuthCommand() {
9493
+ const auth = new Command("auth").description("Authenticate the Omnara CLI.");
9494
+ auth.command("login").description("Authenticate via the browser-based verification flow.").action(async () => {
9495
+ const store = new CredentialsStore();
9496
+ const backendUrl = resolveBackendUrl();
9497
+ try {
9498
+ const configStore = new DaemonConfigStore();
9499
+ await ensureMachineNameConfigured(configStore, logger);
9500
+ await performVerificationLogin({
9501
+ backendUrlOption: backendUrl,
9502
+ frontendUrlOption: resolveFrontendUrl(),
9503
+ store
9504
+ });
9505
+ } catch (error) {
9506
+ Sentry18.captureException(error);
9507
+ if (error instanceof ApiError) {
9508
+ console.error(`
9509
+ Request failed (HTTP ${error.status}): ${error.message}`);
9510
+ } else {
9511
+ console.error(`
9512
+ Login failed: ${extractErrorMessage10(error)}`);
9513
+ }
9514
+ process.exitCode = 1;
9515
+ }
9516
+ });
9517
+ auth.command("logout").description("Remove stored CLI credentials.").action(async () => {
9518
+ const store = new CredentialsStore();
9519
+ await store.clear();
9520
+ console.log("Credentials cleared.");
9521
+ });
9522
+ auth.command("status").description("Show stored CLI credential details.").action(async () => {
9523
+ const store = new CredentialsStore();
9524
+ const credentials = await store.load();
9525
+ if (!credentials) {
9526
+ console.log("No credentials stored.");
9527
+ return;
9528
+ }
9529
+ printStatus(credentials);
9530
+ });
9531
+ return auth;
9532
+ }
9533
+ function printStatus(credentials) {
9534
+ if (credentials.pat) {
9535
+ console.log("Authentication: PAT stored");
9536
+ return;
9537
+ }
9538
+ console.log("Authentication: Not configured");
9539
+ }
9540
+ function extractErrorMessage10(error) {
9541
+ if (error instanceof Error) {
9542
+ return error.message;
9543
+ }
9544
+ return String(error);
9545
+ }
9546
+
9547
+ // src/commands/daemon.ts
9548
+ import { Command as Command2 } from "commander";
9549
+ import * as Sentry24 from "@sentry/node";
9550
+
9463
9551
  // src/daemon/run-daemon.ts
9464
9552
  import * as Sentry23 from "@sentry/node";
9465
9553
 
@@ -9680,7 +9768,7 @@ function extractErrorMessage11(error) {
9680
9768
  // src/daemon/machine-supervisor.ts
9681
9769
  import { randomUUID as randomUUID7 } from "node:crypto";
9682
9770
  import os5 from "node:os";
9683
- import { setTimeout as delay3 } from "node:timers/promises";
9771
+ import { setTimeout as delay4 } from "node:timers/promises";
9684
9772
  import WebSocket2 from "ws";
9685
9773
  import * as Sentry21 from "@sentry/node";
9686
9774
 
@@ -17681,14 +17769,13 @@ var FALLBACK_NAME = pkg.name ?? "omnara";
17681
17769
  var FALLBACK_VERSION = pkg.version ?? "0.0.0";
17682
17770
  var FALLBACK_DESCRIPTION = pkg.description ?? "Command-line interface for Omnara.";
17683
17771
  var PACKAGE_NAME = true ? "omnara" : FALLBACK_NAME;
17684
- var PACKAGE_VERSION = true ? "0.5.0" : FALLBACK_VERSION;
17772
+ var PACKAGE_VERSION = true ? "0.5.2" : FALLBACK_VERSION;
17685
17773
  var PACKAGE_DESCRIPTION = true ? "Command-line interface for Omnara." : FALLBACK_DESCRIPTION;
17686
17774
 
17687
17775
  // src/daemon/machine-supervisor.ts
17688
17776
  var HEARTBEAT_INTERVAL_MS = 1e4;
17689
- var RECONNECT_DELAY_MS = 2e3;
17690
17777
  var LOGIN_WAIT_DELAY_MS2 = 3e4;
17691
- var CONNECT_TIMEOUT_MS = 3e4;
17778
+ var CONNECT_TIMEOUT_MS2 = 3e4;
17692
17779
  var DAEMON_VERSION = PACKAGE_VERSION ?? "0.0.0";
17693
17780
  var MachineSupervisor = class {
17694
17781
  backendUrl;
@@ -17770,11 +17857,13 @@ var MachineSupervisor = class {
17770
17857
  this.running = false;
17771
17858
  }
17772
17859
  async runLoop() {
17860
+ let retryDelayMs = INITIAL_RETRY_DELAY_MS;
17773
17861
  while (!this.stopRequested) {
17774
17862
  try {
17775
17863
  const identity = await this.registerMachine();
17776
17864
  this.clearLoginWait();
17777
17865
  await this.runWebsocket(identity);
17866
+ retryDelayMs = INITIAL_RETRY_DELAY_MS;
17778
17867
  } catch (error) {
17779
17868
  if (this.stopRequested) {
17780
17869
  break;
@@ -17789,11 +17878,14 @@ var MachineSupervisor = class {
17789
17878
  } else {
17790
17879
  this.logger.debug("Machine supervisor still waiting for login\u2026");
17791
17880
  }
17792
- await delay3(LOGIN_WAIT_DELAY_MS2);
17881
+ await delay4(LOGIN_WAIT_DELAY_MS2);
17793
17882
  } else {
17794
17883
  Sentry21.captureException(error);
17795
- this.logger.warn(`Machine supervisor error: ${error.message}`);
17796
- await delay3(RECONNECT_DELAY_MS);
17884
+ this.logger.warn(
17885
+ `Machine supervisor error: ${error.message}. Retrying in ${Math.round(retryDelayMs / 1e3)}s.`
17886
+ );
17887
+ await delay4(retryDelayMs);
17888
+ retryDelayMs = calculateBackoff(retryDelayMs);
17797
17889
  }
17798
17890
  }
17799
17891
  }
@@ -17883,9 +17975,13 @@ var MachineSupervisor = class {
17883
17975
  this.clearHeartbeatTimer();
17884
17976
  this.websocket = null;
17885
17977
  try {
17886
- ws.removeAllListeners();
17887
- if (ws.readyState === WebSocket2.OPEN || ws.readyState === WebSocket2.CONNECTING) {
17978
+ if (ws.readyState === WebSocket2.CONNECTING) {
17979
+ ws.terminate();
17980
+ } else if (ws.readyState === WebSocket2.OPEN) {
17981
+ ws.removeAllListeners();
17888
17982
  ws.close();
17983
+ } else {
17984
+ ws.removeAllListeners();
17889
17985
  }
17890
17986
  } catch {
17891
17987
  }
@@ -17897,7 +17993,7 @@ var MachineSupervisor = class {
17897
17993
  };
17898
17994
  connectTimer = setTimeout(() => {
17899
17995
  finish(new Error("machine websocket connection timed out"));
17900
- }, CONNECT_TIMEOUT_MS);
17996
+ }, CONNECT_TIMEOUT_MS2);
17901
17997
  ws.on("open", () => {
17902
17998
  if (connectTimer) {
17903
17999
  clearTimeout(connectTimer);
@@ -18100,7 +18196,7 @@ var MachineSupervisor = class {
18100
18196
  return;
18101
18197
  }
18102
18198
  }
18103
- await delay3(250);
18199
+ await delay4(250);
18104
18200
  }
18105
18201
  throw new Error("Machine has not finished registering yet. Try again in a few seconds.");
18106
18202
  }
@@ -18418,6 +18514,8 @@ function buildDaemonCommand() {
18418
18514
  daemon.command("start").description("Start the daemon if it is not already running.").option("--no-wait", "Do not wait for the daemon to become ready").action(async (options) => {
18419
18515
  const backendUrl = resolveBackendUrl();
18420
18516
  const frontendUrl = resolveFrontendUrl();
18517
+ const configStore = new DaemonConfigStore();
18518
+ await ensureMachineNameConfigured(configStore, logger);
18421
18519
  await ensureCliCredentials({ backendUrl, frontendUrl, logger });
18422
18520
  await startDaemon({
18423
18521
  logger,
@@ -18734,6 +18832,8 @@ async function launchSessionViaDaemon(options = {}) {
18734
18832
  const backendUrl = resolveBackendUrl(options.backendUrl);
18735
18833
  const frontendUrl = resolveFrontendUrl(options.frontendUrl);
18736
18834
  try {
18835
+ const configStore = new DaemonConfigStore();
18836
+ await ensureMachineNameConfigured(configStore, logger);
18737
18837
  await ensureCliCredentials({ backendUrl, frontendUrl, logger });
18738
18838
  await ensureDaemonForSession({
18739
18839
  cwd: process.cwd(),