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/auth/auth-provider.d.ts +2 -3
- package/dist/auth/credential-manager.d.ts +3 -9
- package/dist/auth/credentials-store.d.ts +1 -0
- package/dist/auth/ensure-cli-credentials.d.ts +0 -5
- package/dist/auth/login-flow.d.ts +0 -4
- package/dist/cli.js +358 -736
- package/dist/cli.js.map +4 -4
- package/dist/daemon/session-pid-registry.d.ts +7 -1
- package/dist/daemon/session-runner.d.ts +0 -2
- package/package.json +1 -2
- package/dist/auth/supabase-client.d.ts +0 -3
- package/dist/auth/supabase-config.d.ts +0 -5
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
|
6198
|
+
async function ensureAuthContext(store, initialCredentials) {
|
|
6267
6199
|
const credentials = initialCredentials ?? await store.load();
|
|
6268
|
-
|
|
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
|
-
|
|
6275
|
-
|
|
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
|
-
|
|
6455
|
-
"
|
|
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
|
-
|
|
6474
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
6485
|
+
Sentry9.captureException(error);
|
|
6792
6486
|
return;
|
|
6793
6487
|
}
|
|
6794
6488
|
if (error) {
|
|
6795
|
-
|
|
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
|
|
6852
|
-
|
|
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
|
-
|
|
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
|
|
6927
|
-
const
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
accessToken
|
|
6931
|
-
|
|
6932
|
-
|
|
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
|
-
|
|
6962
|
-
console.log(`Automatic login failed: ${
|
|
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
|
|
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
|
-
|
|
6995
|
-
console.log(`Loopback listener 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
|
-
|
|
7009
|
-
|
|
6679
|
+
lastBadPat = null;
|
|
6680
|
+
defaultInteractive;
|
|
7010
6681
|
backendUrl;
|
|
7011
|
-
lastBadCredentials = null;
|
|
7012
6682
|
async getAuthContext(options) {
|
|
7013
|
-
const interactive = options?.interactive ??
|
|
7014
|
-
|
|
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.
|
|
6688
|
+
return auth.pat;
|
|
7036
6689
|
}
|
|
7037
6690
|
async loadAuthContext(interactive) {
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
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
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
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
|
|
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
|
-
|
|
6829
|
+
Sentry12.captureException(error);
|
|
7260
6830
|
this.logger.error(
|
|
7261
|
-
`Relay connection failed: ${
|
|
6831
|
+
`Relay connection failed: ${extractErrorMessage5(error)}. Retrying in ${Math.round(backoffMs / 1e3)}s.`
|
|
7262
6832
|
);
|
|
7263
|
-
await
|
|
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
|
-
|
|
7387
|
-
this.logger.error(`Failed to send relay frame: ${
|
|
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
|
-
|
|
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
|
-
|
|
7452
|
-
this.logger.error(`Received invalid JSON from relay: ${
|
|
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
|
-
|
|
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
|
-
|
|
7788
|
-
this.logger.error(`Failed to flush relay queue: ${
|
|
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
|
|
7407
|
+
function delay2(ms) {
|
|
7839
7408
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7840
7409
|
}
|
|
7841
|
-
function
|
|
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 === "
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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: ${
|
|
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.
|
|
8008
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
7650
|
+
Sentry15.captureException(error);
|
|
8099
7651
|
throw new Error(
|
|
8100
|
-
`Failed to read daemon state from ${this.filePath}: ${
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
7907
|
+
Sentry17.captureException(error);
|
|
8356
7908
|
throw new Error(
|
|
8357
|
-
`Failed to read daemon config from ${this.filePath}: ${
|
|
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
|
|
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
|
-
|
|
8029
|
+
Sentry18.captureException(error);
|
|
8478
8030
|
throw new Error(
|
|
8479
|
-
`Directory ${directoryPath} is not accessible: ${
|
|
8031
|
+
`Directory ${directoryPath} is not accessible: ${extractErrorMessage9(error)}`
|
|
8480
8032
|
);
|
|
8481
8033
|
}
|
|
8482
8034
|
}
|
|
8483
8035
|
};
|
|
8484
|
-
function
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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}: ${
|
|
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: ${
|
|
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}: ${
|
|
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
|
|
8736
|
-
|
|
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
|
|
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}: ${
|
|
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: ${
|
|
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: ${
|
|
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
|
|
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
|
|
8470
|
+
import * as Sentry24 from "@sentry/node";
|
|
8838
8471
|
|
|
8839
8472
|
// src/daemon/daemon-service.ts
|
|
8840
|
-
import * as
|
|
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
|
|
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
|
-
|
|
8914
|
-
sendJson(res, 400, { 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
|
-
|
|
8943
|
-
sendJson(res, 400, { 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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
9055
|
-
import { setTimeout as
|
|
8687
|
+
import os5 from "node:os";
|
|
8688
|
+
import { setTimeout as delay3 } from "node:timers/promises";
|
|
9056
8689
|
import WebSocket2 from "ws";
|
|
9057
|
-
import * as
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
9169
|
-
|
|
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
|
|
9175
|
-
if (
|
|
9176
|
-
const alive = await
|
|
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
|
-
|
|
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
|
-
|
|
9214
|
-
|
|
9215
|
-
|
|
9216
|
-
|
|
9217
|
-
|
|
9218
|
-
|
|
9219
|
-
|
|
9220
|
-
|
|
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
|
-
|
|
9228
|
-
|
|
9229
|
-
|
|
9230
|
-
|
|
9231
|
-
|
|
9232
|
-
|
|
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
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
this.
|
|
8843
|
+
this.child = child;
|
|
8844
|
+
child.once("exit", () => {
|
|
8845
|
+
this.onExit();
|
|
8846
|
+
this.child = null;
|
|
9243
8847
|
});
|
|
9244
|
-
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
|
|
9249
|
-
|
|
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:
|
|
9380
|
-
platform:
|
|
9381
|
-
architecture:
|
|
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
|
|
9003
|
+
await delay3(LOGIN_WAIT_DELAY_MS2);
|
|
9416
9004
|
} else {
|
|
9417
|
-
|
|
9005
|
+
Sentry22.captureException(error);
|
|
9418
9006
|
this.logger.warn(`Machine supervisor error: ${error.message}`);
|
|
9419
|
-
await
|
|
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:
|
|
9439
|
-
platform:
|
|
9440
|
-
architecture:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
9140
|
+
finish();
|
|
9520
9141
|
} else {
|
|
9521
|
-
|
|
9142
|
+
finish(new Error("machine websocket closed"));
|
|
9522
9143
|
}
|
|
9523
9144
|
});
|
|
9524
9145
|
ws.on("error", (error) => {
|
|
9525
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
9715
|
-
platform:
|
|
9716
|
-
architecture:
|
|
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 === "
|
|
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 ||
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10028
|
+
Sentry27.captureException(error);
|
|
10407
10029
|
throw error;
|
|
10408
10030
|
}
|
|
10409
10031
|
}
|