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