omnara 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ const __dirname = pathShim.dirname(__filename);
7
7
  // src/cli.ts
8
8
  import path15 from "path";
9
9
  import { Command as Command4 } from "commander";
10
- import * as Sentry28 from "@sentry/node";
10
+ import * as Sentry27 from "@sentry/node";
11
11
 
12
12
  // src/commands/agent.ts
13
13
  import fs5 from "node:fs";
@@ -908,9 +908,7 @@ var CredentialsStore = class {
908
908
  async save(credentials) {
909
909
  await this.withLock(async () => {
910
910
  const payload = {
911
- accessToken: credentials.accessToken,
912
- accessTokenExpiresAt: credentials.accessTokenExpiresAt,
913
- refreshToken: credentials.refreshToken
911
+ pat: credentials.pat
914
912
  };
915
913
  await fs2.mkdir(path3.dirname(this.filePath), { recursive: true, mode: DEFAULT_DIR_MODE });
916
914
  const tempPath = `${this.filePath}.tmp`;
@@ -2494,7 +2492,7 @@ import { randomUUID as randomUUID5 } from "crypto";
2494
2492
  import EventEmitter from "events";
2495
2493
  import WebSocket from "ws";
2496
2494
  import { simpleGit as simpleGit2 } from "simple-git";
2497
- import * as Sentry13 from "@sentry/node";
2495
+ import * as Sentry12 from "@sentry/node";
2498
2496
 
2499
2497
  // ../shared-js/dist/api/base.js
2500
2498
  var ApiError = class extends Error {
@@ -6186,345 +6184,41 @@ async function registerRelaySession(client, accessToken, options) {
6186
6184
  }
6187
6185
 
6188
6186
  // src/auth/auth-provider.ts
6189
- import { setTimeout as delay3 } from "node:timers/promises";
6190
- import * as Sentry12 from "@sentry/node";
6191
-
6192
- // src/auth/credential-manager.ts
6193
- import { setTimeout as delay2 } from "node:timers/promises";
6194
- import * as Sentry8 from "@sentry/node";
6195
-
6196
- // src/auth/supabase-client.ts
6197
- import { createClient } from "@supabase/supabase-js";
6198
-
6199
- // src/package-info.ts
6200
- import { createRequire } from "node:module";
6201
- var require2 = createRequire(import.meta.url);
6202
- var pkg = require2("../package.json");
6203
- var FALLBACK_NAME = pkg.name ?? "omnara";
6204
- var FALLBACK_VERSION = pkg.version ?? "0.0.0";
6205
- var FALLBACK_DESCRIPTION = pkg.description ?? "Command-line interface for Omnara.";
6206
- var PACKAGE_NAME = true ? "omnara" : FALLBACK_NAME;
6207
- var PACKAGE_VERSION = true ? "0.3.1" : FALLBACK_VERSION;
6208
- var PACKAGE_DESCRIPTION = true ? "Command-line interface for Omnara." : FALLBACK_DESCRIPTION;
6209
-
6210
- // src/auth/supabase-client.ts
6211
- var CLIENT_INFO_HEADER = `${PACKAGE_NAME}/${PACKAGE_VERSION}`;
6212
- var singleton = null;
6213
- function getSupabaseClient(config) {
6214
- if (singleton) {
6215
- return singleton;
6216
- }
6217
- singleton = createClient(config.url, config.anonKey, {
6218
- auth: {
6219
- autoRefreshToken: false,
6220
- persistSession: false,
6221
- detectSessionInUrl: false
6222
- },
6223
- global: {
6224
- headers: {
6225
- "X-Client-Info": CLIENT_INFO_HEADER
6226
- }
6227
- }
6228
- });
6229
- return singleton;
6230
- }
6231
-
6232
- // src/auth/supabase-config.ts
6233
- var CACHE = /* @__PURE__ */ new Map();
6234
- async function getSupabaseAuthConfig(baseUrl) {
6235
- const normalized = normalizeBaseUrl2(baseUrl);
6236
- const cached = CACHE.get(normalized);
6237
- if (cached) {
6238
- return cached;
6239
- }
6240
- const client = new ApiClient({ baseUrl: normalized });
6241
- const response = await client.getJson("/api/v1/auth/config");
6242
- const config = {
6243
- url: response.supabase_url,
6244
- anonKey: response.supabase_anon_key
6245
- };
6246
- CACHE.set(normalized, config);
6247
- return config;
6248
- }
6249
- function normalizeBaseUrl2(raw) {
6250
- return raw.endsWith("/") ? raw : `${raw}/`;
6251
- }
6187
+ import * as Sentry11 from "@sentry/node";
6252
6188
 
6253
6189
  // src/auth/credential-manager.ts
6254
- var EXPIRY_BUFFER_MS = 6e4;
6255
- var AUTH_REQUEST_TIMEOUT_MS = 15e3;
6256
6190
  var AuthContextError = class extends Error {
6257
6191
  code;
6258
- status;
6259
6192
  constructor(message, code, options) {
6260
6193
  super(message, { cause: options?.cause });
6261
6194
  this.name = "AuthContextError";
6262
6195
  this.code = code;
6263
- this.status = options?.status;
6264
6196
  }
6265
6197
  };
6266
- async function ensureAuthContext(store, backendUrl, initialCredentials, options = {}) {
6198
+ async function ensureAuthContext(store, initialCredentials) {
6267
6199
  const credentials = initialCredentials ?? await store.load();
6268
- if (!credentials) {
6200
+ const rawPat = credentials?.pat;
6201
+ if (!credentials || rawPat === void 0 || rawPat === null) {
6269
6202
  throw new AuthContextError(
6270
6203
  "No credentials found. Run `omnara auth login` first.",
6271
6204
  "missing_credentials"
6272
6205
  );
6273
6206
  }
6274
- if (!credentials.refreshToken) {
6275
- throw new AuthContextError(
6276
- "Refresh token unavailable. Please run `omnara auth login`.",
6277
- "refresh_token_unavailable"
6278
- );
6279
- }
6280
- if (!credentials.accessToken) {
6281
- return await refreshWithStoredToken(
6282
- store,
6283
- credentials,
6284
- backendUrl,
6285
- "access_token_missing",
6286
- options
6287
- );
6288
- }
6289
- if (!isExpiring(credentials.accessTokenExpiresAt, EXPIRY_BUFFER_MS)) {
6290
- return { credentials, accessToken: credentials.accessToken };
6291
- }
6292
- return await refreshWithStoredToken(
6293
- store,
6294
- credentials,
6295
- backendUrl,
6296
- "access_token_expired",
6297
- options
6298
- );
6299
- }
6300
- async function refreshWithStoredToken(store, credentials, backendUrl, reasonCode, options) {
6301
- const reasonMessage = reasonCode === "access_token_missing" ? "No access token stored." : "Access token expired.";
6302
- return await store.withLock(async () => {
6303
- const latest = await store.load();
6304
- const effective = latest ?? credentials;
6305
- if (effective.accessToken && !isExpiring(effective.accessTokenExpiresAt, EXPIRY_BUFFER_MS)) {
6306
- return { credentials: effective, accessToken: effective.accessToken };
6307
- }
6308
- const refreshToken = effective.refreshToken;
6309
- if (!refreshToken) {
6310
- throw new AuthContextError(
6311
- `Cannot refresh Supabase session because no refresh token is stored. Please run \`omnara auth login\`.`,
6312
- "refresh_token_unavailable"
6313
- );
6314
- }
6315
- const session = await refreshSupabaseSession({
6316
- backendUrl,
6317
- refreshToken,
6318
- reasonMessage,
6319
- logger: options.logger
6320
- });
6321
- const updated = applySession(effective, session);
6322
- await store.save(updated);
6323
- if (!updated.accessToken) {
6324
- throw new AuthContextError(
6325
- "Supabase refresh completed without an access token.",
6326
- "refresh_failed"
6327
- );
6328
- }
6329
- return { credentials: updated, accessToken: updated.accessToken };
6330
- });
6331
- }
6332
- var MAX_REFRESH_ATTEMPTS = 3;
6333
- var REFRESH_RETRY_DELAY_MS = 500;
6334
- var INVALID_REFRESH_ERROR_CODES = /* @__PURE__ */ new Set([
6335
- "invalid_grant",
6336
- "refresh_token_not_found",
6337
- "refresh_token_already_used",
6338
- "session_not_found",
6339
- "session_expired"
6340
- ]);
6341
- var INVALID_REFRESH_ERROR_PHRASES = [
6342
- "invalid refresh token",
6343
- "refresh token not found",
6344
- "refresh_token_not_found",
6345
- "refresh token already used",
6346
- "refresh token is expired",
6347
- "session not found",
6348
- "session expired",
6349
- "invalid_grant"
6350
- ];
6351
- async function refreshSupabaseSession(options) {
6352
- const supabase = await createSupabaseClientWithRetry(
6353
- options.backendUrl,
6354
- options.reasonMessage,
6355
- options.logger
6356
- );
6357
- let attempt = 0;
6358
- let lastError = null;
6359
- while (attempt < MAX_REFRESH_ATTEMPTS) {
6360
- attempt += 1;
6361
- try {
6362
- options.logger?.debug?.(
6363
- `Refreshing Supabase session (attempt ${attempt}/${MAX_REFRESH_ATTEMPTS}).`
6364
- );
6365
- const result = await withTimeout(
6366
- supabase.auth.refreshSession({ refresh_token: options.refreshToken }),
6367
- AUTH_REQUEST_TIMEOUT_MS,
6368
- () => new AuthContextError(
6369
- `${options.reasonMessage} Supabase session refresh timed out after ${AUTH_REQUEST_TIMEOUT_MS}ms.`,
6370
- "network_error"
6371
- )
6372
- );
6373
- const { data, error } = result;
6374
- if (error || !data?.session) {
6375
- const status = typeof error?.status === "number" ? error.status : void 0;
6376
- const loginRequired = isInvalidRefreshTokenError(error);
6377
- const code = loginRequired ? "refresh_failed" : "network_error";
6378
- Sentry8.captureException(error);
6379
- throw new AuthContextError(
6380
- `${options.reasonMessage} Failed to refresh Supabase session: ${error?.message ?? "unknown error"}.`,
6381
- code,
6382
- { cause: error, status }
6383
- );
6384
- }
6385
- return data.session;
6386
- } catch (error) {
6387
- const authError = error instanceof AuthContextError ? error : new AuthContextError(
6388
- `${options.reasonMessage} Failed to refresh Supabase session: ${extractErrorMessage4(error)}.`,
6389
- "network_error",
6390
- { cause: error }
6391
- );
6392
- lastError = authError;
6393
- options.logger?.debug?.(
6394
- `Supabase refresh failed (status ${authError.status ?? "unknown"}): ${authError.message}`
6395
- );
6396
- if (authError.code === "network_error" && attempt < MAX_REFRESH_ATTEMPTS) {
6397
- options.logger?.debug?.("Retrying Supabase refresh after transient error.");
6398
- await delay2(REFRESH_RETRY_DELAY_MS * attempt);
6399
- continue;
6400
- }
6401
- Sentry8.captureException(authError);
6402
- throw authError;
6403
- }
6404
- }
6405
- throw lastError ?? new AuthContextError(
6406
- `${options.reasonMessage} Failed to refresh Supabase session after multiple attempts.`,
6407
- "network_error"
6408
- );
6409
- }
6410
- async function createSupabaseClientWithRetry(backendUrl, reasonMessage, logger3) {
6411
- let attempt = 0;
6412
- let lastError = null;
6413
- while (attempt < MAX_REFRESH_ATTEMPTS) {
6414
- attempt += 1;
6415
- try {
6416
- logger3?.debug?.(
6417
- `Loading Supabase auth configuration (attempt ${attempt}/${MAX_REFRESH_ATTEMPTS}).`
6418
- );
6419
- return await createSupabaseClient(backendUrl, reasonMessage);
6420
- } catch (error) {
6421
- const authError = error instanceof AuthContextError ? error : new AuthContextError(
6422
- `${reasonMessage} Failed to load Supabase configuration: ${extractErrorMessage4(error)}.`,
6423
- "network_error",
6424
- { cause: error }
6425
- );
6426
- lastError = authError;
6427
- if (authError.code === "network_error" && attempt < MAX_REFRESH_ATTEMPTS) {
6428
- logger3?.debug?.("Retrying Supabase auth configuration load after transient error.");
6429
- await delay2(REFRESH_RETRY_DELAY_MS * attempt);
6430
- continue;
6431
- }
6432
- throw authError;
6433
- }
6434
- }
6435
- throw lastError ?? new AuthContextError(
6436
- `${reasonMessage} Failed to load Supabase configuration after multiple attempts.`,
6437
- "network_error"
6438
- );
6439
- }
6440
- async function createSupabaseClient(backendUrl, reasonMessage) {
6441
- try {
6442
- const supabaseConfig = await withTimeout(
6443
- getSupabaseAuthConfig(backendUrl),
6444
- AUTH_REQUEST_TIMEOUT_MS,
6445
- () => new AuthContextError(
6446
- `${reasonMessage} Failed to load Supabase configuration: request timed out after ${AUTH_REQUEST_TIMEOUT_MS}ms.`,
6447
- "network_error"
6448
- )
6449
- );
6450
- return getSupabaseClient(supabaseConfig);
6451
- } catch (error) {
6452
- Sentry8.captureException(error);
6207
+ const pat = rawPat.trim();
6208
+ if (!pat) {
6453
6209
  throw new AuthContextError(
6454
- `${reasonMessage} Failed to load Supabase configuration: ${extractErrorMessage4(error)}.`,
6455
- "network_error",
6456
- { cause: error }
6210
+ "Stored credentials are invalid. Please run `omnara auth login`.",
6211
+ "invalid_credentials"
6457
6212
  );
6458
6213
  }
6459
- }
6460
- function isExpiring(expiresAtIso, bufferMs) {
6461
- if (!expiresAtIso) {
6462
- return true;
6463
- }
6464
- const timestamp = Date.parse(expiresAtIso);
6465
- if (Number.isNaN(timestamp)) {
6466
- return true;
6467
- }
6468
- return timestamp - bufferMs <= Date.now();
6469
- }
6470
- function applySession(credentials, session) {
6471
- const expiresAt = deriveAccessExpiry(session);
6472
6214
  return {
6473
- ...credentials,
6474
- accessToken: session.access_token,
6475
- accessTokenExpiresAt: expiresAt,
6476
- refreshToken: session.refresh_token ?? credentials.refreshToken
6215
+ credentials: { pat },
6216
+ pat
6477
6217
  };
6478
6218
  }
6479
- function deriveAccessExpiry(session) {
6480
- if (session.expires_at) {
6481
- return new Date(session.expires_at * 1e3).toISOString();
6482
- }
6483
- if (session.expires_in) {
6484
- return new Date(Date.now() + session.expires_in * 1e3).toISOString();
6485
- }
6486
- return void 0;
6487
- }
6488
- function extractErrorMessage4(error) {
6489
- if (error instanceof Error && error.message) {
6490
- return error.message;
6491
- }
6492
- return String(error);
6493
- }
6494
- function isInvalidRefreshTokenError(error) {
6495
- if (!error) {
6496
- return false;
6497
- }
6498
- const statusField = error.status;
6499
- const status = typeof statusField === "number" ? statusField : void 0;
6500
- const codeRaw = error.code;
6501
- const code = typeof codeRaw === "string" ? codeRaw.toLowerCase() : "";
6502
- const messageRaw = error.message;
6503
- const message = typeof messageRaw === "string" ? messageRaw.toLowerCase() : "";
6504
- if (INVALID_REFRESH_ERROR_CODES.has(code)) {
6505
- return true;
6506
- }
6507
- if ((status === 400 || status === 401) && INVALID_REFRESH_ERROR_PHRASES.some((phrase) => message.includes(phrase))) {
6508
- return true;
6509
- }
6510
- return false;
6511
- }
6512
- async function withTimeout(promise, timeoutMs, onTimeout) {
6513
- let timeoutHandle = null;
6514
- try {
6515
- const timeoutPromise = new Promise((_, reject) => {
6516
- timeoutHandle = setTimeout(() => reject(onTimeout()), timeoutMs);
6517
- });
6518
- return await Promise.race([promise, timeoutPromise]);
6519
- } finally {
6520
- if (timeoutHandle) {
6521
- clearTimeout(timeoutHandle);
6522
- }
6523
- }
6524
- }
6525
6219
 
6526
6220
  // src/auth/login-flow.ts
6527
- import * as Sentry11 from "@sentry/node";
6221
+ import * as Sentry10 from "@sentry/node";
6528
6222
 
6529
6223
  // src/utils/prompt.ts
6530
6224
  import readline from "readline/promises";
@@ -6603,7 +6297,7 @@ function base64UrlEncode(input2) {
6603
6297
  // src/auth/loopback-listener.ts
6604
6298
  import http from "http";
6605
6299
  import { randomBytes as randomBytes2 } from "crypto";
6606
- import * as Sentry9 from "@sentry/node";
6300
+ import * as Sentry8 from "@sentry/node";
6607
6301
  var CALLBACK_PATH = "/callback";
6608
6302
  var LISTEN_HOST = "127.0.0.1";
6609
6303
  var LoopbackListener = class _LoopbackListener {
@@ -6613,7 +6307,7 @@ var LoopbackListener = class _LoopbackListener {
6613
6307
  await listener.start();
6614
6308
  return listener;
6615
6309
  } catch (error) {
6616
- Sentry9.captureException(error);
6310
+ Sentry8.captureException(error);
6617
6311
  await listener.stop().catch(() => void 0);
6618
6312
  return null;
6619
6313
  }
@@ -6759,7 +6453,7 @@ function buildMessageHtml(message) {
6759
6453
 
6760
6454
  // src/utils/browser.ts
6761
6455
  import { spawn } from "child_process";
6762
- import * as Sentry10 from "@sentry/node";
6456
+ import * as Sentry9 from "@sentry/node";
6763
6457
  var buildBrowserFailureMessage = (url) => `Unable to automatically open ${url}. Please copy and paste this link into your browser.`;
6764
6458
  var escapeForPowerShell = (value) => value.replace(/'/g, "''");
6765
6459
  function tryOpenBrowser(url) {
@@ -6788,11 +6482,11 @@ function tryOpenBrowser(url) {
6788
6482
  if (process.env.DEBUG && error) {
6789
6483
  const details = error instanceof Error ? error.message : String(error);
6790
6484
  console.warn(`${baseMessage} (${details}).`);
6791
- Sentry10.captureException(error);
6485
+ Sentry9.captureException(error);
6792
6486
  return;
6793
6487
  }
6794
6488
  if (error) {
6795
- Sentry10.captureException(error);
6489
+ Sentry9.captureException(error);
6796
6490
  }
6797
6491
  console.warn(baseMessage);
6798
6492
  };
@@ -6848,9 +6542,8 @@ async function performVerificationLogin({
6848
6542
  code,
6849
6543
  codeVerifier ? { codeVerifier } : {}
6850
6544
  );
6851
- const updated = mergeCredentialsAfterExchange({
6852
- exchange
6853
- });
6545
+ const pat = await issuePatToken(client, exchange.accessToken);
6546
+ const updated = { pat };
6854
6547
  await store.save(updated);
6855
6548
  latestCredentials = updated;
6856
6549
  if (!successLogged) {
@@ -6897,7 +6590,7 @@ async function performVerificationLogin({
6897
6590
  break;
6898
6591
  }
6899
6592
  }
6900
- Sentry11.captureException(error);
6593
+ Sentry10.captureException(error);
6901
6594
  await loopback?.stop();
6902
6595
  throw error;
6903
6596
  }
@@ -6923,33 +6616,13 @@ function resolveFrontendUrl(option) {
6923
6616
  const candidate = option ?? process.env.OMNARA_FRONTEND_URL ?? DEFAULT_FRONTEND_URL;
6924
6617
  return normalizeBaseUrl(candidate);
6925
6618
  }
6926
- function mergeCredentialsAfterExchange(args) {
6927
- const now = Date.now();
6928
- const accessExpiresAt = new Date(now + args.exchange.accessTokenExpiresIn * 1e3).toISOString();
6929
- return {
6930
- accessToken: args.exchange.accessToken,
6931
- accessTokenExpiresAt: accessExpiresAt,
6932
- refreshToken: args.exchange.refreshToken
6933
- };
6934
- }
6935
- function formatDuration(ms) {
6936
- const totalSeconds = Math.round(ms / 1e3);
6937
- if (totalSeconds < 60) {
6938
- return `${totalSeconds}s`;
6939
- }
6940
- const totalMinutes = Math.round(totalSeconds / 60);
6941
- if (totalMinutes < 60) {
6942
- const seconds = totalSeconds % 60;
6943
- return seconds > 0 ? `${totalMinutes}m ${seconds}s` : `${totalMinutes}m`;
6944
- }
6945
- const totalHours = Math.floor(totalMinutes / 60);
6946
- const minutes = totalMinutes % 60;
6947
- if (totalHours < 48) {
6948
- return minutes > 0 ? `${totalHours}h ${minutes}m` : `${totalHours}h`;
6949
- }
6950
- const totalDays = Math.floor(totalHours / 24);
6951
- const hours = totalDays % 24;
6952
- return hours > 0 ? `${totalDays}d ${hours}h` : `${totalDays}d`;
6619
+ async function issuePatToken(client, accessToken) {
6620
+ const response = await client.postJson(
6621
+ "/api/v1/auth/pat",
6622
+ {},
6623
+ { accessToken }
6624
+ );
6625
+ return response.token;
6953
6626
  }
6954
6627
  function reportLoopbackError(error) {
6955
6628
  if (error instanceof ApiError) {
@@ -6958,8 +6631,8 @@ function reportLoopbackError(error) {
6958
6631
  );
6959
6632
  return;
6960
6633
  }
6961
- Sentry11.captureException(error);
6962
- console.log(`Automatic login failed: ${extractErrorMessage5(error)}`);
6634
+ Sentry10.captureException(error);
6635
+ console.log(`Automatic login failed: ${extractErrorMessage4(error)}`);
6963
6636
  }
6964
6637
  function printSessionInstructions(session) {
6965
6638
  console.log(
@@ -6968,7 +6641,7 @@ function printSessionInstructions(session) {
6968
6641
  console.log(chalk.blueBright.underline(`${session.verificationUriComplete}`));
6969
6642
  console.log(chalk.blue("\nApprove the request, then return to the CLI."));
6970
6643
  }
6971
- function extractErrorMessage5(error) {
6644
+ function extractErrorMessage4(error) {
6972
6645
  if (error instanceof Error && error.message) {
6973
6646
  return error.message;
6974
6647
  }
@@ -6991,96 +6664,57 @@ async function waitForLoopback(listener, sessionId, onCode) {
6991
6664
  reportLoopbackError(error);
6992
6665
  }
6993
6666
  } catch (error) {
6994
- Sentry11.captureException(error);
6995
- console.log(`Loopback listener error: ${extractErrorMessage5(error)}`);
6667
+ Sentry10.captureException(error);
6668
+ console.log(`Loopback listener error: ${extractErrorMessage4(error)}`);
6996
6669
  }
6997
6670
  }
6998
6671
 
6999
6672
  // src/auth/auth-provider.ts
7000
- var TOKEN_REFRESH_BUFFER_MS = 6e4;
7001
- var AUTH_RETRY_BASE_MS = 500;
7002
- var AUTH_RETRY_MAX_MS = 5e3;
7003
6673
  var AuthProvider = class {
7004
6674
  constructor(options) {
7005
6675
  this.options = options;
7006
6676
  this.backendUrl = normalizeBaseUrl(options.backendUrl);
6677
+ this.defaultInteractive = options.interactive ?? true;
7007
6678
  }
7008
- cached = null;
7009
- inflight = null;
6679
+ lastBadPat = null;
6680
+ defaultInteractive;
7010
6681
  backendUrl;
7011
- lastBadCredentials = null;
7012
6682
  async getAuthContext(options) {
7013
- const interactive = options?.interactive ?? true;
7014
- const snapshot = await this.options.store.load();
7015
- const useCache = this.cached && isAccessTokenFresh(this.cached) && credentialsEqual(this.cached.credentials, snapshot ?? null);
7016
- if (useCache && this.cached) {
7017
- return this.cached;
7018
- }
7019
- let inflight = this.inflight;
7020
- if (!inflight) {
7021
- const pending = this.loadAuthContext(interactive);
7022
- inflight = pending.finally(() => {
7023
- if (this.inflight === inflight) {
7024
- this.inflight = null;
7025
- }
7026
- });
7027
- this.inflight = inflight;
7028
- }
7029
- const auth = await inflight;
7030
- this.cached = auth;
7031
- return auth;
6683
+ const interactive = options?.interactive ?? this.defaultInteractive;
6684
+ return await this.loadAuthContext(interactive);
7032
6685
  }
7033
6686
  async getAccessToken(options) {
7034
6687
  const auth = await this.getAuthContext(options);
7035
- return auth.accessToken;
6688
+ return auth.pat;
7036
6689
  }
7037
6690
  async loadAuthContext(interactive) {
7038
- let attempt = 0;
7039
- while (true) {
7040
- attempt += 1;
7041
- const snapshot = await this.options.store.load();
7042
- if (this.lastBadCredentials && credentialsEqual(snapshot ?? null, this.lastBadCredentials)) {
7043
- throw new AuthContextError(
7044
- "Stored credentials unchanged since invalid refresh token. Please log in.",
7045
- "refresh_failed"
7046
- );
6691
+ const snapshot = await this.options.store.load();
6692
+ if (this.lastBadPat && snapshot?.pat === this.lastBadPat) {
6693
+ throw new AuthContextError(
6694
+ "Stored credentials unchanged since invalid PAT. Please log in.",
6695
+ "invalid_credentials"
6696
+ );
6697
+ }
6698
+ try {
6699
+ const context = await ensureAuthContext(this.options.store, snapshot);
6700
+ this.lastBadPat = null;
6701
+ return context;
6702
+ } catch (error) {
6703
+ const authError = error instanceof AuthContextError ? error : null;
6704
+ if (authError?.code === "invalid_credentials") {
6705
+ this.lastBadPat = snapshot?.pat ?? null;
7047
6706
  }
7048
- try {
7049
- const context = await ensureAuthContext(this.options.store, this.backendUrl, snapshot, {
7050
- logger: this.options.logger
7051
- });
7052
- this.cached = context;
7053
- this.lastBadCredentials = null;
7054
- return context;
7055
- } catch (error) {
7056
- const authError = error instanceof AuthContextError ? error : null;
7057
- if (authError?.code === "refresh_failed") {
7058
- this.lastBadCredentials = snapshot ?? null;
7059
- }
7060
- if (authError) {
7061
- Sentry12.captureException(authError);
7062
- } else {
7063
- Sentry12.captureException(error);
7064
- }
7065
- if (authError && shouldAttemptLogin(authError)) {
7066
- if (!interactive) {
7067
- throw authError;
7068
- }
7069
- await this.performLogin();
7070
- continue;
7071
- }
7072
- if (authError && !shouldRetryAuth(authError)) {
7073
- throw authError;
7074
- }
7075
- const delayMs = Math.min(
7076
- AUTH_RETRY_BASE_MS * 2 ** Math.min(attempt - 1, 6),
7077
- AUTH_RETRY_MAX_MS
7078
- );
7079
- this.options.logger?.debug?.(
7080
- `Auth transient error (${extractErrorMessage6(authError ?? error)}). Retrying in ${delayMs}ms\u2026`
7081
- );
7082
- await delay3(delayMs + Math.floor(Math.random() * 100));
6707
+ if (authError) {
6708
+ Sentry11.captureException(authError);
6709
+ } else {
6710
+ Sentry11.captureException(error);
7083
6711
  }
6712
+ if (!interactive) {
6713
+ throw authError ?? error;
6714
+ }
6715
+ await this.performLogin();
6716
+ const updated = await this.options.store.load();
6717
+ return await ensureAuthContext(this.options.store, updated);
7084
6718
  }
7085
6719
  }
7086
6720
  async performLogin() {
@@ -7093,74 +6727,10 @@ var AuthProvider = class {
7093
6727
  });
7094
6728
  }
7095
6729
  };
7096
- function isAccessTokenFresh(context) {
7097
- const expiresAt = context.credentials.accessTokenExpiresAt;
7098
- if (!expiresAt) {
7099
- return false;
7100
- }
7101
- const timestamp = Date.parse(expiresAt);
7102
- if (Number.isNaN(timestamp)) {
7103
- return false;
7104
- }
7105
- return timestamp - TOKEN_REFRESH_BUFFER_MS > Date.now();
7106
- }
7107
- function shouldAttemptLogin(error) {
7108
- if (error instanceof AuthContextError) {
7109
- return authContextErrorRequiresLogin(error);
7110
- }
7111
- return false;
7112
- }
7113
- function authContextErrorRequiresLogin(error) {
7114
- switch (error.code) {
7115
- case "missing_credentials":
7116
- case "refresh_token_unavailable":
7117
- case "refresh_failed":
7118
- return true;
7119
- default:
7120
- return false;
7121
- }
7122
- }
7123
- function shouldRetryAuth(error) {
7124
- if (error.code !== "network_error") {
7125
- return false;
7126
- }
7127
- if (typeof error.status === "number" && error.status === 429) {
7128
- return true;
7129
- }
7130
- if (isLockErrorMessage(error.message)) {
7131
- return true;
7132
- }
7133
- if (typeof error.status === "number" && error.status >= 500) {
7134
- return true;
7135
- }
7136
- if (typeof error.status !== "number") {
7137
- return true;
7138
- }
7139
- return false;
7140
- }
7141
- function credentialsEqual(a, b) {
7142
- if (!a && !b) {
7143
- return true;
7144
- }
7145
- if (!a || !b) {
7146
- return false;
7147
- }
7148
- return a.accessToken === b.accessToken && a.accessTokenExpiresAt === b.accessTokenExpiresAt && a.refreshToken === b.refreshToken;
7149
- }
7150
6730
  async function createAuthProvider(options) {
7151
6731
  const provider = new AuthProvider(options);
7152
6732
  return provider;
7153
6733
  }
7154
- function isLockErrorMessage(message) {
7155
- const lower = message.toLowerCase();
7156
- return lower.includes("credentials lock") || lower.includes("acquiring credentials lock");
7157
- }
7158
- function extractErrorMessage6(error) {
7159
- if (error instanceof Error && error.message) {
7160
- return error.message;
7161
- }
7162
- return String(error);
7163
- }
7164
6734
 
7165
6735
  // src/http/user-sessions.ts
7166
6736
  async function ensureUserSession(client, accessToken, options) {
@@ -7246,7 +6816,7 @@ var RelayBridge = class extends EventEmitter {
7246
6816
  } else {
7247
6817
  this.logger.debug("Relay bridge still waiting for credentials...");
7248
6818
  }
7249
- await delay4(LOGIN_WAIT_DELAY_MS);
6819
+ await delay2(LOGIN_WAIT_DELAY_MS);
7250
6820
  continue;
7251
6821
  }
7252
6822
  const aborted = isAbortError(error);
@@ -7256,11 +6826,11 @@ var RelayBridge = class extends EventEmitter {
7256
6826
  }
7257
6827
  break;
7258
6828
  }
7259
- Sentry13.captureException(error);
6829
+ Sentry12.captureException(error);
7260
6830
  this.logger.error(
7261
- `Relay connection failed: ${extractErrorMessage7(error)}. Retrying in ${Math.round(backoffMs / 1e3)}s.`
6831
+ `Relay connection failed: ${extractErrorMessage5(error)}. Retrying in ${Math.round(backoffMs / 1e3)}s.`
7262
6832
  );
7263
- await delay4(backoffMs);
6833
+ await delay2(backoffMs);
7264
6834
  backoffMs = Math.min(backoffMs * 2, 3e4);
7265
6835
  }
7266
6836
  }
@@ -7297,7 +6867,6 @@ var RelayBridge = class extends EventEmitter {
7297
6867
  const url = buildRelayWebsocketUrl(this.backendUrl, registration.relayWsPath);
7298
6868
  const ws = new WebSocket(url, {
7299
6869
  headers: {
7300
- authorization: `Bearer ${accessToken}`,
7301
6870
  "x-relay-token": registration.relayToken
7302
6871
  }
7303
6872
  });
@@ -7383,8 +6952,8 @@ var RelayBridge = class extends EventEmitter {
7383
6952
  target.send(serialized);
7384
6953
  return;
7385
6954
  } catch (error) {
7386
- Sentry13.captureException(error);
7387
- this.logger.error(`Failed to send relay frame: ${extractErrorMessage7(error)}`);
6955
+ Sentry12.captureException(error);
6956
+ this.logger.error(`Failed to send relay frame: ${extractErrorMessage5(error)}`);
7388
6957
  if (target === this.ws) {
7389
6958
  this.closeCurrentSocket();
7390
6959
  }
@@ -7417,7 +6986,7 @@ var RelayBridge = class extends EventEmitter {
7417
6986
  };
7418
6987
  const onError = (error) => {
7419
6988
  if (!this.stopped) {
7420
- Sentry13.captureException(error);
6989
+ Sentry12.captureException(error);
7421
6990
  this.logger.error(`Relay websocket error: ${error.message}`);
7422
6991
  }
7423
6992
  };
@@ -7448,8 +7017,8 @@ var RelayBridge = class extends EventEmitter {
7448
7017
  const text = typeof data === "string" ? data : data.toString("utf8");
7449
7018
  parsed = JSON.parse(text);
7450
7019
  } catch (error) {
7451
- Sentry13.captureException(error);
7452
- this.logger.error(`Received invalid JSON from relay: ${extractErrorMessage7(error)}`);
7020
+ Sentry12.captureException(error);
7021
+ this.logger.error(`Received invalid JSON from relay: ${extractErrorMessage5(error)}`);
7453
7022
  return;
7454
7023
  }
7455
7024
  if (this.tryHandleControlMessage(parsed)) {
@@ -7674,7 +7243,7 @@ var RelayBridge = class extends EventEmitter {
7674
7243
  diff: diff.length > 0 ? diff : null
7675
7244
  };
7676
7245
  } catch (error) {
7677
- Sentry13.captureException(error);
7246
+ Sentry12.captureException(error);
7678
7247
  this.logger.error(`Failed to get git diff for branch ${branchName}:`, error);
7679
7248
  return {
7680
7249
  type: "git_diff",
@@ -7784,8 +7353,8 @@ var RelayBridge = class extends EventEmitter {
7784
7353
  try {
7785
7354
  this.ws.send(payload);
7786
7355
  } catch (error) {
7787
- Sentry13.captureException(error);
7788
- this.logger.error(`Failed to flush relay queue: ${extractErrorMessage7(error)}`);
7356
+ Sentry12.captureException(error);
7357
+ this.logger.error(`Failed to flush relay queue: ${extractErrorMessage5(error)}`);
7789
7358
  this.pendingOutbound.unshift(payload);
7790
7359
  this.closeCurrentSocket();
7791
7360
  break;
@@ -7835,10 +7404,10 @@ function buildRelayWebsocketUrl(baseUrl, path16) {
7835
7404
  }
7836
7405
  return url.toString();
7837
7406
  }
7838
- function delay4(ms) {
7407
+ function delay2(ms) {
7839
7408
  return new Promise((resolve) => setTimeout(resolve, ms));
7840
7409
  }
7841
- function extractErrorMessage7(error) {
7410
+ function extractErrorMessage5(error) {
7842
7411
  if (error instanceof Error) {
7843
7412
  return error.message;
7844
7413
  }
@@ -7874,14 +7443,14 @@ function waitForOpen(ws) {
7874
7443
  });
7875
7444
  }
7876
7445
  function isLoginRequiredError(error) {
7877
- return error instanceof AuthContextError && (error.code === "missing_credentials" || error.code === "refresh_token_unavailable" || error.code === "refresh_failed");
7446
+ return error instanceof AuthContextError && (error.code === "missing_credentials" || error.code === "invalid_credentials");
7878
7447
  }
7879
7448
  function isUnauthorizedApiError(error) {
7880
7449
  return error instanceof ApiError && (error.status === 401 || error.status === 403);
7881
7450
  }
7882
7451
 
7883
7452
  // src/commands/agent.ts
7884
- import * as Sentry14 from "@sentry/node";
7453
+ import * as Sentry13 from "@sentry/node";
7885
7454
  async function runAgentOrchestrator(options = {}) {
7886
7455
  const log = buildOrchestratorLogger();
7887
7456
  const orchestrator = await startAgentOrchestrator();
@@ -7911,7 +7480,7 @@ async function runAgentOrchestrator(options = {}) {
7911
7480
  try {
7912
7481
  await bridge.start();
7913
7482
  } catch (error) {
7914
- Sentry14.captureException(error);
7483
+ Sentry13.captureException(error);
7915
7484
  log.error(`Relay orchestrator terminated: ${extractErrorMessage(error)}`);
7916
7485
  process.exitCode = process.exitCode || 1;
7917
7486
  } finally {
@@ -7930,7 +7499,7 @@ function createSignalHandler(onStop, log) {
7930
7499
  log.important(`
7931
7500
  Received ${signal}. Shutting down agent orchestrator...`);
7932
7501
  void onStop().catch((error) => {
7933
- Sentry14.captureException(error);
7502
+ Sentry13.captureException(error);
7934
7503
  log.error("Error while shutting down agent orchestrator:", error);
7935
7504
  });
7936
7505
  };
@@ -7963,7 +7532,7 @@ function buildOrchestratorLogger() {
7963
7532
 
7964
7533
  // src/commands/auth.ts
7965
7534
  import { Command } from "commander";
7966
- import * as Sentry15 from "@sentry/node";
7535
+ import * as Sentry14 from "@sentry/node";
7967
7536
  function buildAuthCommand() {
7968
7537
  const auth = new Command("auth").description("Authenticate the Omnara CLI.");
7969
7538
  auth.command("login").description("Authenticate via the browser-based verification flow.").action(async () => {
@@ -7976,13 +7545,13 @@ function buildAuthCommand() {
7976
7545
  store
7977
7546
  });
7978
7547
  } catch (error) {
7979
- Sentry15.captureException(error);
7548
+ Sentry14.captureException(error);
7980
7549
  if (error instanceof ApiError) {
7981
7550
  console.error(`
7982
7551
  Request failed (HTTP ${error.status}): ${error.message}`);
7983
7552
  } else {
7984
7553
  console.error(`
7985
- Login failed: ${extractErrorMessage8(error)}`);
7554
+ Login failed: ${extractErrorMessage6(error)}`);
7986
7555
  }
7987
7556
  process.exitCode = 1;
7988
7557
  }
@@ -8004,30 +7573,13 @@ Login failed: ${extractErrorMessage8(error)}`);
8004
7573
  return auth;
8005
7574
  }
8006
7575
  function printStatus(credentials) {
8007
- if (credentials.accessToken) {
8008
- const accessStatus = describeExpiry(credentials.accessTokenExpiresAt);
8009
- const refreshStatus = credentials.refreshToken ? "stored" : "missing";
8010
- console.log(
8011
- `Authentication: Supabase access token (${accessStatus}), Refresh token (${refreshStatus})`
8012
- );
7576
+ if (credentials.pat) {
7577
+ console.log("Authentication: PAT stored");
8013
7578
  return;
8014
7579
  }
8015
7580
  console.log("Authentication: Not configured");
8016
7581
  }
8017
- function describeExpiry(iso) {
8018
- if (!iso) {
8019
- return "unknown expiry";
8020
- }
8021
- const timestamp = Date.parse(iso);
8022
- if (Number.isNaN(timestamp)) {
8023
- return "invalid timestamp";
8024
- }
8025
- const diff = timestamp - Date.now();
8026
- const abs = Math.abs(diff);
8027
- const descriptor = formatDuration(abs);
8028
- return diff >= 0 ? `expires in ${descriptor}` : `expired ${descriptor} ago`;
8029
- }
8030
- function extractErrorMessage8(error) {
7582
+ function extractErrorMessage6(error) {
8031
7583
  if (error instanceof Error) {
8032
7584
  return error.message;
8033
7585
  }
@@ -8036,22 +7588,22 @@ function extractErrorMessage8(error) {
8036
7588
 
8037
7589
  // src/commands/daemon.ts
8038
7590
  import { Command as Command2 } from "commander";
8039
- import * as Sentry26 from "@sentry/node";
7591
+ import * as Sentry25 from "@sentry/node";
8040
7592
 
8041
7593
  // src/daemon/daemon-launcher.ts
8042
7594
  import { spawn as spawn2, exec as execCb } from "node:child_process";
8043
7595
  import path12 from "node:path";
8044
- import os3 from "node:os";
8045
- import { promisify } from "node:util";
7596
+ import os4 from "node:os";
7597
+ import { promisify as promisify2 } from "node:util";
8046
7598
 
8047
7599
  // src/daemon/daemon-client.ts
8048
7600
  import http2 from "node:http";
8049
- import * as Sentry17 from "@sentry/node";
7601
+ import * as Sentry16 from "@sentry/node";
8050
7602
 
8051
7603
  // src/daemon/state-store.ts
8052
7604
  import fs7 from "node:fs/promises";
8053
7605
  import path8 from "node:path";
8054
- import * as Sentry16 from "@sentry/node";
7606
+ import * as Sentry15 from "@sentry/node";
8055
7607
 
8056
7608
  // src/daemon/paths.ts
8057
7609
  import fs6 from "node:fs/promises";
@@ -8095,9 +7647,9 @@ var DaemonStateStore = class {
8095
7647
  if (isFileNotFound2(error)) {
8096
7648
  return null;
8097
7649
  }
8098
- Sentry16.captureException(error);
7650
+ Sentry15.captureException(error);
8099
7651
  throw new Error(
8100
- `Failed to read daemon state from ${this.filePath}: ${extractErrorMessage9(error)}`
7652
+ `Failed to read daemon state from ${this.filePath}: ${extractErrorMessage7(error)}`
8101
7653
  );
8102
7654
  }
8103
7655
  }
@@ -8116,7 +7668,7 @@ var DaemonStateStore = class {
8116
7668
  await fs7.unlink(this.filePath);
8117
7669
  } catch (error) {
8118
7670
  if (!isFileNotFound2(error)) {
8119
- Sentry16.captureException(error);
7671
+ Sentry15.captureException(error);
8120
7672
  throw error;
8121
7673
  }
8122
7674
  }
@@ -8127,7 +7679,7 @@ function isFileNotFound2(error) {
8127
7679
  error && typeof error === "object" && "code" in error && error.code === "ENOENT"
8128
7680
  );
8129
7681
  }
8130
- function extractErrorMessage9(error) {
7682
+ function extractErrorMessage7(error) {
8131
7683
  if (error instanceof Error && error.message) {
8132
7684
  return error.message;
8133
7685
  }
@@ -8154,7 +7706,7 @@ var DaemonClient = class {
8154
7706
  await this.stateStore.clear();
8155
7707
  return null;
8156
7708
  }
8157
- Sentry17.captureException(error);
7709
+ Sentry16.captureException(error);
8158
7710
  throw error;
8159
7711
  }
8160
7712
  }
@@ -8174,7 +7726,7 @@ var DaemonClient = class {
8174
7726
  await this.stateStore.clear();
8175
7727
  return null;
8176
7728
  }
8177
- Sentry17.captureException(error);
7729
+ Sentry16.captureException(error);
8178
7730
  throw error;
8179
7731
  }
8180
7732
  }
@@ -8197,7 +7749,7 @@ var DaemonClient = class {
8197
7749
  await this.stateStore.clear();
8198
7750
  return false;
8199
7751
  }
8200
- Sentry17.captureException(error);
7752
+ Sentry16.captureException(error);
8201
7753
  throw error;
8202
7754
  }
8203
7755
  }
@@ -8221,7 +7773,7 @@ var DaemonClient = class {
8221
7773
  await this.stateStore.clear();
8222
7774
  return null;
8223
7775
  }
8224
- Sentry17.captureException(error);
7776
+ Sentry16.captureException(error);
8225
7777
  throw error;
8226
7778
  }
8227
7779
  }
@@ -8295,7 +7847,7 @@ function isProcessAlive2(pid) {
8295
7847
  if (isErrno2(error, "ESRCH")) {
8296
7848
  return false;
8297
7849
  }
8298
- Sentry17.captureException(error);
7850
+ Sentry16.captureException(error);
8299
7851
  return false;
8300
7852
  }
8301
7853
  }
@@ -8328,13 +7880,13 @@ var HttpError = class extends Error {
8328
7880
  // src/daemon/directory-registry.ts
8329
7881
  import fs9 from "node:fs/promises";
8330
7882
  import path10 from "node:path";
8331
- import * as Sentry19 from "@sentry/node";
7883
+ import * as Sentry18 from "@sentry/node";
8332
7884
 
8333
7885
  // src/daemon/config-store.ts
8334
7886
  import fs8 from "node:fs/promises";
8335
7887
  import path9 from "node:path";
8336
7888
  import { randomUUID as randomUUID6 } from "node:crypto";
8337
- import * as Sentry18 from "@sentry/node";
7889
+ import * as Sentry17 from "@sentry/node";
8338
7890
  var DEFAULT_CONFIG = {
8339
7891
  directories: [],
8340
7892
  settings: {}
@@ -8352,9 +7904,9 @@ var DaemonConfigStore = class {
8352
7904
  if (isFileNotFound3(error)) {
8353
7905
  return cloneConfig(DEFAULT_CONFIG);
8354
7906
  }
8355
- Sentry18.captureException(error);
7907
+ Sentry17.captureException(error);
8356
7908
  throw new Error(
8357
- `Failed to read daemon config from ${this.filePath}: ${extractErrorMessage10(error)}`
7909
+ `Failed to read daemon config from ${this.filePath}: ${extractErrorMessage8(error)}`
8358
7910
  );
8359
7911
  }
8360
7912
  }
@@ -8436,7 +7988,7 @@ function isFileNotFound3(error) {
8436
7988
  error && typeof error === "object" && "code" in error && error.code === "ENOENT"
8437
7989
  );
8438
7990
  }
8439
- function extractErrorMessage10(error) {
7991
+ function extractErrorMessage8(error) {
8440
7992
  if (error instanceof Error && error.message) {
8441
7993
  return error.message;
8442
7994
  }
@@ -8474,14 +8026,14 @@ var DirectoryRegistry = class {
8474
8026
  throw new Error("Path is not a directory");
8475
8027
  }
8476
8028
  } catch (error) {
8477
- Sentry19.captureException(error);
8029
+ Sentry18.captureException(error);
8478
8030
  throw new Error(
8479
- `Directory ${directoryPath} is not accessible: ${extractErrorMessage11(error)}`
8031
+ `Directory ${directoryPath} is not accessible: ${extractErrorMessage9(error)}`
8480
8032
  );
8481
8033
  }
8482
8034
  }
8483
8035
  };
8484
- function extractErrorMessage11(error) {
8036
+ function extractErrorMessage9(error) {
8485
8037
  if (error instanceof Error && error.message) {
8486
8038
  return error.message;
8487
8039
  }
@@ -8494,9 +8046,14 @@ import chalk2 from "chalk";
8494
8046
  // src/daemon/session-pid-registry.ts
8495
8047
  import fs10 from "node:fs/promises";
8496
8048
  import path11 from "node:path";
8497
- import * as Sentry20 from "@sentry/node";
8049
+ import os3 from "node:os";
8050
+ import { execFile } from "node:child_process";
8051
+ import { promisify } from "node:util";
8052
+ import * as Sentry19 from "@sentry/node";
8498
8053
  var REGISTRY_PATH = path11.join(OMNARA_HOME_DIR, "session-processes.json");
8499
8054
  var LOCK_PATH = `${REGISTRY_PATH}.lock`;
8055
+ var execFileAsync = promisify(execFile);
8056
+ var START_TIME_TOLERANCE_MS = 5e3;
8500
8057
  async function acquireLock(retries = 20, delayMs = 50) {
8501
8058
  for (let attempt = 0; attempt < retries; attempt += 1) {
8502
8059
  try {
@@ -8504,7 +8061,7 @@ async function acquireLock(retries = 20, delayMs = 50) {
8504
8061
  return handle;
8505
8062
  } catch (error) {
8506
8063
  if (error.code !== "EEXIST") {
8507
- Sentry20.captureException(error);
8064
+ Sentry19.captureException(error);
8508
8065
  throw error;
8509
8066
  }
8510
8067
  await new Promise((resolve) => setTimeout(resolve, delayMs));
@@ -8519,12 +8076,29 @@ async function releaseLock(handle) {
8519
8076
  async function readRegistry() {
8520
8077
  try {
8521
8078
  const raw = await fs10.readFile(REGISTRY_PATH, "utf8");
8522
- return JSON.parse(raw);
8079
+ const parsed = JSON.parse(raw);
8080
+ const normalized = {};
8081
+ for (const [sessionId, value] of Object.entries(parsed)) {
8082
+ if (typeof value === "number") {
8083
+ normalized[sessionId] = {
8084
+ pid: value
8085
+ };
8086
+ continue;
8087
+ }
8088
+ if (value && typeof value.pid === "number") {
8089
+ normalized[sessionId] = {
8090
+ pid: value.pid,
8091
+ startedAt: typeof value.startedAt === "string" ? value.startedAt : void 0,
8092
+ command: typeof value.command === "string" ? value.command : void 0
8093
+ };
8094
+ }
8095
+ }
8096
+ return normalized;
8523
8097
  } catch (error) {
8524
8098
  if (error.code === "ENOENT") {
8525
8099
  return {};
8526
8100
  }
8527
- Sentry20.captureException(error);
8101
+ Sentry19.captureException(error);
8528
8102
  throw error;
8529
8103
  }
8530
8104
  }
@@ -8552,13 +8126,73 @@ async function isProcessAlive3(pid) {
8552
8126
  if (error.code === "ESRCH") {
8553
8127
  return false;
8554
8128
  }
8555
- Sentry20.captureException(error);
8129
+ Sentry19.captureException(error);
8130
+ return true;
8131
+ }
8132
+ }
8133
+ async function isSessionProcessAlive(record) {
8134
+ const alive = await isProcessAlive3(record.pid);
8135
+ if (!alive) {
8136
+ return false;
8137
+ }
8138
+ const tokensMatch = await matchesExpectedCommand(record.pid);
8139
+ if (!tokensMatch) {
8140
+ return false;
8141
+ }
8142
+ const timeMatch = await matchesStartTime(record.pid, record.startedAt);
8143
+ if (record.startedAt && !timeMatch) {
8144
+ return false;
8145
+ }
8146
+ return true;
8147
+ }
8148
+ async function matchesExpectedCommand(pid) {
8149
+ if (os3.platform() === "win32") {
8150
+ return true;
8151
+ }
8152
+ try {
8153
+ const { stdout } = await execFileAsync("ps", ["-p", String(pid), "-o", "command="]);
8154
+ const command = stdout.trim().toLowerCase();
8155
+ if (!command) {
8156
+ return false;
8157
+ }
8158
+ if (!command.includes("omnara") || !command.includes("daemon run-session")) {
8159
+ return false;
8160
+ }
8161
+ return true;
8162
+ } catch {
8163
+ return false;
8164
+ }
8165
+ }
8166
+ async function matchesStartTime(pid, recordedStartedAt) {
8167
+ if (os3.platform() === "win32") {
8168
+ return true;
8169
+ }
8170
+ if (!recordedStartedAt) {
8171
+ return true;
8172
+ }
8173
+ const recordedMs = Date.parse(recordedStartedAt);
8174
+ if (Number.isNaN(recordedMs)) {
8175
+ return true;
8176
+ }
8177
+ try {
8178
+ const { stdout } = await execFileAsync("ps", ["-p", String(pid), "-o", "lstart="]);
8179
+ const psStart = stdout.trim();
8180
+ if (!psStart) {
8181
+ return true;
8182
+ }
8183
+ const psDate = new Date(psStart);
8184
+ if (Number.isNaN(psDate.getTime())) {
8185
+ return true;
8186
+ }
8187
+ const delta = Math.abs(psDate.getTime() - recordedMs);
8188
+ return delta <= START_TIME_TOLERANCE_MS;
8189
+ } catch {
8556
8190
  return true;
8557
8191
  }
8558
8192
  }
8559
8193
 
8560
8194
  // src/daemon/daemon-launcher.ts
8561
- var exec = promisify(execCb);
8195
+ var exec = promisify2(execCb);
8562
8196
  var DEFAULT_WAIT_MS = 1e4;
8563
8197
  async function ensureDaemonForSession(options = {}) {
8564
8198
  const logger3 = options.logger ?? logger;
@@ -8598,7 +8232,7 @@ async function ensureMachineNameConfigured(configStore, logger3) {
8598
8232
  process.env.OMNARA_MACHINE_NAME = config.machine.name;
8599
8233
  return config.machine.name;
8600
8234
  }
8601
- const defaultName = os3.hostname();
8235
+ const defaultName = os4.hostname();
8602
8236
  let machineName = defaultName;
8603
8237
  if (process.stdin.isTTY && process.stdout.isTTY) {
8604
8238
  const answer = (await prompt(chalk2.blue(`Enter a name for this machine [${defaultName}]: `))).trim();
@@ -8701,13 +8335,13 @@ async function killAllDaemonProcesses(options = {}) {
8701
8335
  process.kill(pid, "SIGKILL");
8702
8336
  logger3.success(`Killed process ${pid}`);
8703
8337
  } catch (error) {
8704
- logger3.warn(`Failed to kill ${pid}: ${extractErrorMessage12(error)}`);
8338
+ logger3.warn(`Failed to kill ${pid}: ${extractErrorMessage10(error)}`);
8705
8339
  }
8706
8340
  }
8707
8341
  await stateStore.clear();
8708
8342
  logger3.debug(`Cleared ${envLabel} daemon state file`);
8709
8343
  } catch (error) {
8710
- logger3.error(`Failed to kill ${envLabel} daemon processes: ${extractErrorMessage12(error)}`);
8344
+ logger3.error(`Failed to kill ${envLabel} daemon processes: ${extractErrorMessage10(error)}`);
8711
8345
  }
8712
8346
  }
8713
8347
  }
@@ -8724,7 +8358,7 @@ async function killDaemonByState(options = {}) {
8724
8358
  process.kill(state.pid, "SIGKILL");
8725
8359
  logger3.success(`Killed daemon process ${state.pid}`);
8726
8360
  } catch (error) {
8727
- logger3.warn(`Failed to kill daemon ${state.pid}: ${extractErrorMessage12(error)}`);
8361
+ logger3.warn(`Failed to kill daemon ${state.pid}: ${extractErrorMessage10(error)}`);
8728
8362
  }
8729
8363
  await stateStore.clear();
8730
8364
  }
@@ -8732,26 +8366,25 @@ async function killSessionsByRegistry(options = {}) {
8732
8366
  const logger3 = options.logger ?? logger;
8733
8367
  try {
8734
8368
  await withSessionRegistry(async (registry) => {
8735
- const pids = Object.values(registry);
8736
- for (const pid of pids) {
8737
- if (!Number.isFinite(pid)) {
8369
+ for (const record of Object.values(registry)) {
8370
+ if (!record || !Number.isFinite(record.pid)) {
8738
8371
  continue;
8739
8372
  }
8740
- const alive = await isProcessAlive3(pid);
8373
+ const alive = await isSessionProcessAlive(record);
8741
8374
  if (!alive) {
8742
8375
  continue;
8743
8376
  }
8744
8377
  try {
8745
- process.kill(pid, "SIGKILL");
8746
- logger3.success(`Killed session process ${pid}`);
8378
+ process.kill(record.pid, "SIGKILL");
8379
+ logger3.success(`Killed session process ${record.pid}`);
8747
8380
  } catch (error) {
8748
- logger3.warn(`Failed to kill session ${pid}: ${extractErrorMessage12(error)}`);
8381
+ logger3.warn(`Failed to kill session ${record.pid}: ${extractErrorMessage10(error)}`);
8749
8382
  }
8750
8383
  }
8751
8384
  return [{}, void 0];
8752
8385
  });
8753
8386
  } catch (error) {
8754
- logger3.warn(`Failed to kill sessions via registry: ${extractErrorMessage12(error)}`);
8387
+ logger3.warn(`Failed to kill sessions via registry: ${extractErrorMessage10(error)}`);
8755
8388
  }
8756
8389
  }
8757
8390
  async function findDaemonRelatedPids() {
@@ -8788,7 +8421,7 @@ async function registerDirectory(directoryPath, options) {
8788
8421
  options.logger.warn("Daemon was unavailable; storing directory locally.");
8789
8422
  } catch (error) {
8790
8423
  options.logger.warn(
8791
- `Failed to register ${normalized} with daemon: ${extractErrorMessage12(error)}`
8424
+ `Failed to register ${normalized} with daemon: ${extractErrorMessage10(error)}`
8792
8425
  );
8793
8426
  }
8794
8427
  const registry = new DirectoryRegistry();
@@ -8826,7 +8459,7 @@ async function waitForReady(client, options = {}) {
8826
8459
  logger3.warn("Timed out waiting for daemon readiness.");
8827
8460
  return null;
8828
8461
  }
8829
- function extractErrorMessage12(error) {
8462
+ function extractErrorMessage10(error) {
8830
8463
  if (error instanceof Error && error.message) {
8831
8464
  return error.message;
8832
8465
  }
@@ -8834,16 +8467,16 @@ function extractErrorMessage12(error) {
8834
8467
  }
8835
8468
 
8836
8469
  // src/daemon/run-daemon.ts
8837
- import * as Sentry25 from "@sentry/node";
8470
+ import * as Sentry24 from "@sentry/node";
8838
8471
 
8839
8472
  // src/daemon/daemon-service.ts
8840
- import * as Sentry24 from "@sentry/node";
8473
+ import * as Sentry23 from "@sentry/node";
8841
8474
 
8842
8475
  // src/daemon/local-control-server.ts
8843
8476
  import http3 from "node:http";
8844
8477
  import { randomBytes as randomBytes3 } from "node:crypto";
8845
8478
  import { URL as URL2 } from "node:url";
8846
- import * as Sentry21 from "@sentry/node";
8479
+ import * as Sentry20 from "@sentry/node";
8847
8480
  var MIN_PORT = 1e4;
8848
8481
  var MAX_PORT = 65535;
8849
8482
  var MAX_ATTEMPTS = 100;
@@ -8910,8 +8543,8 @@ function createRequestHandler(context) {
8910
8543
  });
8911
8544
  sendJson(res, 200, { directory: result.entry, created: result.created });
8912
8545
  } catch (error) {
8913
- Sentry21.captureException(error);
8914
- sendJson(res, 400, { error: extractErrorMessage13(error) });
8546
+ Sentry20.captureException(error);
8547
+ sendJson(res, 400, { error: extractErrorMessage11(error) });
8915
8548
  }
8916
8549
  return;
8917
8550
  }
@@ -8939,8 +8572,8 @@ function createRequestHandler(context) {
8939
8572
  });
8940
8573
  return;
8941
8574
  }
8942
- Sentry21.captureException(error);
8943
- sendJson(res, 400, { error: extractErrorMessage13(error) });
8575
+ Sentry20.captureException(error);
8576
+ sendJson(res, 400, { error: extractErrorMessage11(error) });
8944
8577
  }
8945
8578
  return;
8946
8579
  }
@@ -8948,14 +8581,14 @@ function createRequestHandler(context) {
8948
8581
  const body = await readJsonBody(req);
8949
8582
  sendJson(res, 202, { status: "stopping" });
8950
8583
  Promise.resolve().then(() => context.onStopRequested(body?.status ?? "OFFLINE")).catch((error) => {
8951
- Sentry21.captureException(error);
8584
+ Sentry20.captureException(error);
8952
8585
  context.logger?.error("Failed to stop daemon:", error);
8953
8586
  });
8954
8587
  return;
8955
8588
  }
8956
8589
  sendJson(res, 404, { error: "not_found" });
8957
8590
  } catch (error) {
8958
- Sentry21.captureException(error);
8591
+ Sentry20.captureException(error);
8959
8592
  context.logger?.error("Control server error:", error);
8960
8593
  if (!res.headersSent) {
8961
8594
  sendJson(res, 500, { error: "internal_error" });
@@ -9042,7 +8675,7 @@ function isPortInUse(error) {
9042
8675
  error && typeof error === "object" && "code" in error && error.code === "EADDRINUSE"
9043
8676
  ) || false;
9044
8677
  }
9045
- function extractErrorMessage13(error) {
8678
+ function extractErrorMessage11(error) {
9046
8679
  if (error instanceof Error && error.message) {
9047
8680
  return error.message;
9048
8681
  }
@@ -9051,10 +8684,10 @@ function extractErrorMessage13(error) {
9051
8684
 
9052
8685
  // src/daemon/machine-supervisor.ts
9053
8686
  import { randomUUID as randomUUID7 } from "node:crypto";
9054
- import os4 from "node:os";
9055
- import { setTimeout as delay5 } from "node:timers/promises";
8687
+ import os5 from "node:os";
8688
+ import { setTimeout as delay3 } from "node:timers/promises";
9056
8689
  import WebSocket2 from "ws";
9057
- import * as Sentry23 from "@sentry/node";
8690
+ import * as Sentry22 from "@sentry/node";
9058
8691
 
9059
8692
  // src/http/machines.ts
9060
8693
  async function registerMachine(client, accessToken, payload) {
@@ -9089,14 +8722,14 @@ async function ensureWorkspace(client, accessToken, payload) {
9089
8722
 
9090
8723
  // src/daemon/session-runner.ts
9091
8724
  import { spawn as spawn3 } from "node:child_process";
9092
- import * as Sentry22 from "@sentry/node";
8725
+ import * as Sentry21 from "@sentry/node";
8726
+ import fs11 from "node:fs";
9093
8727
  import path13 from "node:path";
9094
8728
  var SessionManager = class {
9095
8729
  backendUrl;
9096
8730
  frontendUrl;
9097
8731
  directoryRegistry;
9098
8732
  logger;
9099
- runners = /* @__PURE__ */ new Map();
9100
8733
  constructor(options) {
9101
8734
  this.backendUrl = options.backendUrl;
9102
8735
  this.frontendUrl = options.frontendUrl;
@@ -9104,9 +8737,6 @@ var SessionManager = class {
9104
8737
  this.logger = options.logger;
9105
8738
  }
9106
8739
  async startSession(spec) {
9107
- if (this.runners.has(spec.launchId)) {
9108
- throw new Error("Session is already running on this machine");
9109
- }
9110
8740
  const entry = await this.directoryRegistry.getEntry(spec.directory);
9111
8741
  if (!entry) {
9112
8742
  throw new Error(`Directory ${spec.directory} is not registered on this machine`);
@@ -9118,23 +8748,15 @@ var SessionManager = class {
9118
8748
  frontendUrl: this.frontendUrl,
9119
8749
  logger: this.logger,
9120
8750
  onExit: () => {
9121
- this.runners.delete(spec.launchId);
9122
8751
  }
9123
8752
  });
9124
- this.runners.set(spec.launchId, runner);
9125
8753
  try {
9126
8754
  await runner.start();
9127
8755
  } catch (error) {
9128
- this.runners.delete(spec.launchId);
9129
- Sentry22.captureException(error);
8756
+ Sentry21.captureException(error);
9130
8757
  throw error;
9131
8758
  }
9132
8759
  }
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
8760
  };
9139
8761
  var SessionRunner = class {
9140
8762
  spec;
@@ -9143,12 +8765,7 @@ var SessionRunner = class {
9143
8765
  frontendUrl;
9144
8766
  logger;
9145
8767
  onExit;
9146
- startedAt = null;
9147
8768
  child = null;
9148
- exitPromise = null;
9149
- resolveExit = null;
9150
- exitHandled = false;
9151
- sessionLogPath = null;
9152
8769
  constructor(options) {
9153
8770
  this.spec = options.spec;
9154
8771
  this.directory = options.directory;
@@ -9165,15 +8782,12 @@ var SessionRunner = class {
9165
8782
  if (!entry) {
9166
8783
  throw new Error("Unable to determine CLI entry path for session runner");
9167
8784
  }
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}`);
8785
+ const sessionLogPath = await createSessionLogPath(this.spec.workspaceId, this.spec.launchId);
8786
+ this.logger.info(`Session ${this.spec.launchId} logs available at ${sessionLogPath}`);
9173
8787
  await withSessionRegistry(async (registry) => {
9174
- const existingPid = registry[this.spec.userSessionId];
9175
- if (existingPid) {
9176
- const alive = await isProcessAlive3(existingPid);
8788
+ const existing = registry[this.spec.userSessionId];
8789
+ if (existing) {
8790
+ const alive = await isSessionProcessAlive(existing);
9177
8791
  if (alive) {
9178
8792
  throw new Error(`User session ${this.spec.userSessionId} is already running`);
9179
8793
  }
@@ -9199,7 +8813,7 @@ var SessionRunner = class {
9199
8813
  OMNARA_WORKSPACE_ID: this.spec.workspaceId,
9200
8814
  OMNARA_WORKSPACE_DIR: this.directory.path,
9201
8815
  OMNARA_USER_SESSION_ID: this.spec.userSessionId,
9202
- ...this.sessionLogPath ? { OMNARA_SESSION_LOG_PATH: this.sessionLogPath } : {}
8816
+ OMNARA_SESSION_LOG_PATH: sessionLogPath
9203
8817
  };
9204
8818
  if (this.spec.workspaceConfig) {
9205
8819
  env.OMNARA_WORKSPACE_CONFIG = JSON.stringify(this.spec.workspaceConfig);
@@ -9210,95 +8824,58 @@ var SessionRunner = class {
9210
8824
  if (this.spec.metadata) {
9211
8825
  env.OMNARA_WORKSPACE_METADATA = JSON.stringify(this.spec.metadata);
9212
8826
  }
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();
8827
+ let logFd = null;
8828
+ try {
8829
+ logFd = fs11.openSync(sessionLogPath, "a");
8830
+ const child = spawn3(process.execPath, args, {
8831
+ cwd: this.directory.path,
8832
+ stdio: ["ignore", logFd, logFd],
8833
+ env,
8834
+ detached: true
9226
8835
  });
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
- }
8836
+ fs11.closeSync(logFd);
8837
+ logFd = null;
8838
+ child.unref();
8839
+ await new Promise((resolve, reject) => {
8840
+ child.once("spawn", resolve);
8841
+ child.once("error", reject);
9238
8842
  });
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);
8843
+ this.child = child;
8844
+ child.once("exit", () => {
8845
+ this.onExit();
8846
+ this.child = null;
9243
8847
  });
9244
- });
9245
- await withSessionRegistry(async (registry) => {
9246
- registry[this.spec.userSessionId] = child.pid ?? -1;
9247
- return [registry, null];
9248
- });
9249
- await spawnPromise;
8848
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
8849
+ const command = `${process.execPath} ${args.join(" ")}`;
8850
+ await withSessionRegistry(async (registry) => {
8851
+ registry[this.spec.userSessionId] = {
8852
+ pid: child.pid ?? -1,
8853
+ startedAt,
8854
+ command
8855
+ };
8856
+ return [registry, null];
8857
+ });
8858
+ } catch (error) {
8859
+ if (logFd !== null) {
8860
+ try {
8861
+ fs11.closeSync(logFd);
8862
+ } catch {
8863
+ }
8864
+ }
8865
+ Sentry21.captureException(error);
8866
+ this.logger.error("Failed to start session process:", error);
8867
+ throw error;
8868
+ }
9250
8869
  }
9251
8870
  async stop() {
9252
8871
  if (!this.child) {
9253
- await this.exitPromise;
9254
8872
  return;
9255
8873
  }
9256
8874
  if (!this.child.killed) {
9257
8875
  this.child.kill("SIGINT");
9258
8876
  }
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
8877
  }
9292
8878
  };
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
8879
  async function createSessionLogPath(workspaceId, launchId) {
9303
8880
  await ensureOmnaraHome();
9304
8881
  const workspaceDir = path13.join(USER_SESSION_LOG_ROOT, workspaceId);
@@ -9308,10 +8885,22 @@ async function createSessionLogPath(workspaceId, launchId) {
9308
8885
  return logPath;
9309
8886
  }
9310
8887
 
8888
+ // src/package-info.ts
8889
+ import { createRequire } from "node:module";
8890
+ var require2 = createRequire(import.meta.url);
8891
+ var pkg = require2("../package.json");
8892
+ var FALLBACK_NAME = pkg.name ?? "omnara";
8893
+ var FALLBACK_VERSION = pkg.version ?? "0.0.0";
8894
+ var FALLBACK_DESCRIPTION = pkg.description ?? "Command-line interface for Omnara.";
8895
+ var PACKAGE_NAME = true ? "omnara" : FALLBACK_NAME;
8896
+ var PACKAGE_VERSION = true ? "0.4.0" : FALLBACK_VERSION;
8897
+ var PACKAGE_DESCRIPTION = true ? "Command-line interface for Omnara." : FALLBACK_DESCRIPTION;
8898
+
9311
8899
  // src/daemon/machine-supervisor.ts
9312
8900
  var HEARTBEAT_INTERVAL_MS = 1e4;
9313
8901
  var RECONNECT_DELAY_MS = 2e3;
9314
8902
  var LOGIN_WAIT_DELAY_MS2 = 3e4;
8903
+ var CONNECT_TIMEOUT_MS = 3e4;
9315
8904
  var DAEMON_VERSION = PACKAGE_VERSION ?? "0.0.0";
9316
8905
  var MachineSupervisor = class {
9317
8906
  backendUrl;
@@ -9376,15 +8965,14 @@ var MachineSupervisor = class {
9376
8965
  type: "heartbeat",
9377
8966
  status,
9378
8967
  metadata: {
9379
- hostname: os4.hostname(),
9380
- platform: os4.platform(),
9381
- architecture: os4.arch(),
8968
+ hostname: os5.hostname(),
8969
+ platform: os5.platform(),
8970
+ architecture: os5.arch(),
9382
8971
  daemon_version: DAEMON_VERSION
9383
8972
  },
9384
8973
  directories: directories.map((entry) => entry.path)
9385
8974
  };
9386
8975
  await this.sendMessage(payload);
9387
- await this.sessionManager.stopAll();
9388
8976
  await this.closeWebsocket();
9389
8977
  if (this.loopPromise) {
9390
8978
  await this.loopPromise;
@@ -9412,11 +9000,11 @@ var MachineSupervisor = class {
9412
9000
  } else {
9413
9001
  this.logger.debug("Machine supervisor still waiting for login\u2026");
9414
9002
  }
9415
- await delay5(LOGIN_WAIT_DELAY_MS2);
9003
+ await delay3(LOGIN_WAIT_DELAY_MS2);
9416
9004
  } else {
9417
- Sentry23.captureException(error);
9005
+ Sentry22.captureException(error);
9418
9006
  this.logger.warn(`Machine supervisor error: ${error.message}`);
9419
- await delay5(RECONNECT_DELAY_MS);
9007
+ await delay3(RECONNECT_DELAY_MS);
9420
9008
  }
9421
9009
  }
9422
9010
  }
@@ -9435,9 +9023,9 @@ var MachineSupervisor = class {
9435
9023
  const payload = {
9436
9024
  machine_id: config.machine?.id,
9437
9025
  machine_name: machineName,
9438
- hostname: os4.hostname(),
9439
- platform: os4.platform(),
9440
- architecture: os4.arch(),
9026
+ hostname: os5.hostname(),
9027
+ platform: os5.platform(),
9028
+ architecture: os5.arch(),
9441
9029
  daemon_version: DAEMON_VERSION,
9442
9030
  directories: directories.map((entry) => entry.path),
9443
9031
  metadata: {
@@ -9459,7 +9047,7 @@ var MachineSupervisor = class {
9459
9047
  });
9460
9048
  return await this.registerMachine();
9461
9049
  }
9462
- Sentry23.captureException(error);
9050
+ Sentry22.captureException(error);
9463
9051
  throw error;
9464
9052
  }
9465
9053
  const now = /* @__PURE__ */ new Date();
@@ -9494,7 +9082,38 @@ var MachineSupervisor = class {
9494
9082
  }
9495
9083
  });
9496
9084
  this.websocket = ws;
9085
+ let done = false;
9086
+ let connectTimer = null;
9087
+ const finish = (error) => {
9088
+ if (done) return;
9089
+ done = true;
9090
+ if (connectTimer) {
9091
+ clearTimeout(connectTimer);
9092
+ connectTimer = null;
9093
+ }
9094
+ this.clearHeartbeatTimer();
9095
+ this.websocket = null;
9096
+ try {
9097
+ ws.removeAllListeners();
9098
+ if (ws.readyState === WebSocket2.OPEN || ws.readyState === WebSocket2.CONNECTING) {
9099
+ ws.close();
9100
+ }
9101
+ } catch {
9102
+ }
9103
+ if (error) {
9104
+ reject(error);
9105
+ } else {
9106
+ resolve();
9107
+ }
9108
+ };
9109
+ connectTimer = setTimeout(() => {
9110
+ finish(new Error("machine websocket connection timed out"));
9111
+ }, CONNECT_TIMEOUT_MS);
9497
9112
  ws.on("open", () => {
9113
+ if (connectTimer) {
9114
+ clearTimeout(connectTimer);
9115
+ connectTimer = null;
9116
+ }
9498
9117
  this.logger.success("\u2713 Machine daemon connected to backend");
9499
9118
  void this.sendHeartbeat().catch((error) => {
9500
9119
  this.logger.error("Failed to send heartbeat:", error);
@@ -9505,25 +9124,28 @@ var MachineSupervisor = class {
9505
9124
  try {
9506
9125
  this.handleSocketMessage(data);
9507
9126
  } catch (error) {
9508
- Sentry23.captureException(error);
9127
+ Sentry22.captureException(error);
9509
9128
  this.logger.error("Failed to process machine message:", error);
9510
9129
  }
9511
9130
  });
9512
9131
  ws.on("close", (code, reason) => {
9132
+ if (connectTimer) {
9133
+ clearTimeout(connectTimer);
9134
+ connectTimer = null;
9135
+ }
9513
9136
  this.logger.warn(
9514
9137
  `Machine websocket closed (${code}${reason ? `: ${reason.toString()}` : ""})`
9515
9138
  );
9516
- this.clearHeartbeatTimer();
9517
- this.websocket = null;
9518
9139
  if (this.stopRequested) {
9519
- resolve();
9140
+ finish();
9520
9141
  } else {
9521
- reject(new Error("machine websocket closed"));
9142
+ finish(new Error("machine websocket closed"));
9522
9143
  }
9523
9144
  });
9524
9145
  ws.on("error", (error) => {
9525
- Sentry23.captureException(error);
9146
+ Sentry22.captureException(error);
9526
9147
  this.logger.error("Machine websocket error:", error);
9148
+ finish(new Error("machine websocket error"));
9527
9149
  });
9528
9150
  });
9529
9151
  }
@@ -9563,7 +9185,7 @@ var MachineSupervisor = class {
9563
9185
  });
9564
9186
  return;
9565
9187
  }
9566
- Sentry23.captureException(error);
9188
+ Sentry22.captureException(error);
9567
9189
  const messageText = error instanceof Error ? error.message : String(error);
9568
9190
  await this.sendRpcResponse(message.request_id, "error", messageText);
9569
9191
  }
@@ -9647,7 +9269,7 @@ var MachineSupervisor = class {
9647
9269
  await this.recoverMachineRegistration("workspace ensure returned 404");
9648
9270
  return await this.ensureWorkspaceWithRetry(directory, metadata, attempt + 1);
9649
9271
  }
9650
- Sentry23.captureException(error);
9272
+ Sentry22.captureException(error);
9651
9273
  throw error;
9652
9274
  }
9653
9275
  }
@@ -9686,7 +9308,7 @@ var MachineSupervisor = class {
9686
9308
  return;
9687
9309
  }
9688
9310
  }
9689
- await delay5(250);
9311
+ await delay3(250);
9690
9312
  }
9691
9313
  throw new Error("Machine has not finished registering yet. Try again in a few seconds.");
9692
9314
  }
@@ -9694,7 +9316,7 @@ var MachineSupervisor = class {
9694
9316
  this.clearHeartbeatTimer();
9695
9317
  this.heartbeatTimer = setInterval(() => {
9696
9318
  void this.sendHeartbeat().catch((error) => {
9697
- Sentry23.captureException(error);
9319
+ Sentry22.captureException(error);
9698
9320
  this.logger.error("Failed to send heartbeat:", error);
9699
9321
  });
9700
9322
  }, HEARTBEAT_INTERVAL_MS);
@@ -9711,9 +9333,9 @@ var MachineSupervisor = class {
9711
9333
  type: "heartbeat",
9712
9334
  status: "ONLINE",
9713
9335
  metadata: {
9714
- hostname: os4.hostname(),
9715
- platform: os4.platform(),
9716
- architecture: os4.arch(),
9336
+ hostname: os5.hostname(),
9337
+ platform: os5.platform(),
9338
+ architecture: os5.arch(),
9717
9339
  daemon_version: DAEMON_VERSION
9718
9340
  },
9719
9341
  directories: directories.map((entry) => entry.path)
@@ -9776,13 +9398,13 @@ function buildMachineWebsocketUrl(baseUrl, path16) {
9776
9398
  return url.toString();
9777
9399
  }
9778
9400
  function isLoginRequiredError2(error) {
9779
- return error instanceof AuthContextError && (error.code === "missing_credentials" || error.code === "refresh_token_unavailable" || error.code === "refresh_failed");
9401
+ return error instanceof AuthContextError && (error.code === "missing_credentials" || error.code === "invalid_credentials");
9780
9402
  }
9781
9403
  function isUnauthorizedApiError2(error) {
9782
9404
  return error instanceof ApiError && (error.status === 401 || error.status === 403);
9783
9405
  }
9784
9406
  function deriveMachineName() {
9785
- return process.env.OMNARA_MACHINE_NAME || os4.hostname();
9407
+ return process.env.OMNARA_MACHINE_NAME || os5.hostname();
9786
9408
  }
9787
9409
  function decodeJson(raw) {
9788
9410
  const text = typeof raw === "string" ? raw : raw.toString("utf8");
@@ -9834,7 +9456,7 @@ var DaemonService = class {
9834
9456
  this.resolveStopPromise = null;
9835
9457
  await this.machineSupervisor?.stop("OFFLINE").catch(() => void 0);
9836
9458
  await this.cleanupState();
9837
- Sentry24.captureException(error);
9459
+ Sentry23.captureException(error);
9838
9460
  throw error;
9839
9461
  }
9840
9462
  }
@@ -9902,7 +9524,7 @@ var DaemonService = class {
9902
9524
  };
9903
9525
 
9904
9526
  // src/daemon/run-daemon.ts
9905
- import fs11 from "node:fs";
9527
+ import fs12 from "node:fs";
9906
9528
  import path14 from "node:path";
9907
9529
  async function runDaemonProcess() {
9908
9530
  await ensureOmnaraHome();
@@ -9910,7 +9532,7 @@ async function runDaemonProcess() {
9910
9532
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
9911
9533
  const fileName = `${timestamp}-${process.pid}.log`;
9912
9534
  const logPath = path14.join(DAEMON_LOG_DIR, fileName);
9913
- const stream = fs11.createWriteStream(logPath, {
9535
+ const stream = fs12.createWriteStream(logPath, {
9914
9536
  flags: "a",
9915
9537
  mode: DEFAULT_FILE_MODE2
9916
9538
  });
@@ -9959,7 +9581,7 @@ async function runDaemonProcess() {
9959
9581
  return () => {
9960
9582
  logger3.warn(`Received ${signal}. Stopping daemon...`);
9961
9583
  void service.stop().catch((error) => {
9962
- Sentry25.captureException(error);
9584
+ Sentry24.captureException(error);
9963
9585
  logger3.error("Failed to stop daemon:", error);
9964
9586
  });
9965
9587
  };
@@ -9972,7 +9594,7 @@ async function runDaemonProcess() {
9972
9594
  await service.start();
9973
9595
  await service.waitForShutdown();
9974
9596
  } catch (error) {
9975
- Sentry25.captureException(error);
9597
+ Sentry24.captureException(error);
9976
9598
  logger3.error("Daemon exited with error:", error);
9977
9599
  process.exitCode = 1;
9978
9600
  } finally {
@@ -10027,7 +9649,7 @@ function buildDaemonCommand() {
10027
9649
  try {
10028
9650
  process.chdir(options.directory);
10029
9651
  } catch (error) {
10030
- Sentry26.captureException(error);
9652
+ Sentry25.captureException(error);
10031
9653
  logger.error(
10032
9654
  `Unable to switch to directory ${options.directory}: ${error.message}`
10033
9655
  );
@@ -10045,7 +9667,7 @@ function buildDaemonCommand() {
10045
9667
  userSessionId: options.userSessionId
10046
9668
  });
10047
9669
  } catch (error) {
10048
- Sentry26.captureException(error);
9670
+ Sentry25.captureException(error);
10049
9671
  logger.error("User session exited with error:", error);
10050
9672
  process.exitCode = process.exitCode || 1;
10051
9673
  }
@@ -10080,7 +9702,7 @@ function buildKillCommand() {
10080
9702
  // src/utils/version-check.ts
10081
9703
  import chalk3 from "chalk";
10082
9704
  import { execSync } from "child_process";
10083
- import * as Sentry27 from "@sentry/node";
9705
+ import * as Sentry26 from "@sentry/node";
10084
9706
  async function fetchLatestVersion(packageName) {
10085
9707
  const controller = new AbortController();
10086
9708
  const timeout = setTimeout(() => controller.abort(), 3e3);
@@ -10174,13 +9796,13 @@ $ sudo npm install -g ${packageName}@latest`,
10174
9796
  "\n"
10175
9797
  )
10176
9798
  );
10177
- Sentry27.captureException(err);
9799
+ Sentry26.captureException(err);
10178
9800
  }
10179
9801
  } else {
10180
9802
  console.log(chalk3.grey("Omnara is up to date!\n"));
10181
9803
  }
10182
9804
  } catch (e) {
10183
- Sentry27.captureException(e);
9805
+ Sentry26.captureException(e);
10184
9806
  console.log(e);
10185
9807
  }
10186
9808
  return false;
@@ -10292,7 +9914,7 @@ async function run(argv) {
10292
9914
  try {
10293
9915
  await program.parseAsync(argv);
10294
9916
  } catch (error) {
10295
- Sentry28.captureException(error);
9917
+ Sentry27.captureException(error);
10296
9918
  program.error(error.message, { exitCode: 1 });
10297
9919
  }
10298
9920
  }
@@ -10306,7 +9928,7 @@ function isMainModule() {
10306
9928
  }
10307
9929
  if (isMainModule()) {
10308
9930
  run(process.argv).catch((error) => {
10309
- Sentry28.captureException(error);
9931
+ Sentry27.captureException(error);
10310
9932
  logger.error(error);
10311
9933
  process.exitCode = 1;
10312
9934
  });
@@ -10382,7 +10004,7 @@ ${chalk5.bold("Chat via the Omnara mobile app or web dashboard")}: ${chalk5.unde
10382
10004
  process.exitCode = 1;
10383
10005
  return;
10384
10006
  }
10385
- Sentry28.captureException(error);
10007
+ Sentry27.captureException(error);
10386
10008
  logger.warn(`Failed to prepare daemon: ${error.message}`);
10387
10009
  }
10388
10010
  }
@@ -10403,7 +10025,7 @@ async function requestDaemonLaunch(client, payload, options) {
10403
10025
  if (isRecoverableLaunchError(error)) {
10404
10026
  options.logger.debug("Daemon not ready for launch yet:", error.message);
10405
10027
  } else {
10406
- Sentry28.captureException(error);
10028
+ Sentry27.captureException(error);
10407
10029
  throw error;
10408
10030
  }
10409
10031
  }