omnara 0.5.0 → 0.5.1
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 +217 -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,36 @@ 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
|
+
if (ws.readyState === WebSocket.CONNECTING) {
|
|
8499
|
+
ws.terminate();
|
|
8500
|
+
}
|
|
8501
|
+
reject(error);
|
|
8502
|
+
} else {
|
|
8503
|
+
resolve();
|
|
8504
|
+
}
|
|
8505
|
+
};
|
|
8506
|
+
const onOpen = () => settle();
|
|
8507
|
+
const onError = (error) => settle(error);
|
|
8432
8508
|
ws.once("open", onOpen);
|
|
8433
8509
|
ws.once("error", onError);
|
|
8510
|
+
timer = setTimeout(() => {
|
|
8511
|
+
settle(new Error("WebSocket connection timed out"));
|
|
8512
|
+
}, CONNECT_TIMEOUT_MS);
|
|
8434
8513
|
});
|
|
8435
8514
|
}
|
|
8436
8515
|
function isLoginRequiredError(error) {
|
|
@@ -8525,63 +8604,7 @@ function buildOrchestratorLogger() {
|
|
|
8525
8604
|
|
|
8526
8605
|
// src/commands/auth.ts
|
|
8527
8606
|
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";
|
|
8607
|
+
import * as Sentry18 from "@sentry/node";
|
|
8585
8608
|
|
|
8586
8609
|
// src/daemon/daemon-launcher.ts
|
|
8587
8610
|
import { spawn as spawn2, exec as execCb } from "node:child_process";
|
|
@@ -8591,12 +8614,12 @@ import { promisify as promisify2 } from "node:util";
|
|
|
8591
8614
|
|
|
8592
8615
|
// src/daemon/daemon-client.ts
|
|
8593
8616
|
import http2 from "node:http";
|
|
8594
|
-
import * as
|
|
8617
|
+
import * as Sentry14 from "@sentry/node";
|
|
8595
8618
|
|
|
8596
8619
|
// src/daemon/state-store.ts
|
|
8597
8620
|
import fs7 from "node:fs/promises";
|
|
8598
8621
|
import path8 from "node:path";
|
|
8599
|
-
import * as
|
|
8622
|
+
import * as Sentry13 from "@sentry/node";
|
|
8600
8623
|
|
|
8601
8624
|
// src/daemon/paths.ts
|
|
8602
8625
|
import fs6 from "node:fs/promises";
|
|
@@ -8640,9 +8663,9 @@ var DaemonStateStore = class {
|
|
|
8640
8663
|
if (isFileNotFound2(error)) {
|
|
8641
8664
|
return null;
|
|
8642
8665
|
}
|
|
8643
|
-
|
|
8666
|
+
Sentry13.captureException(error);
|
|
8644
8667
|
throw new Error(
|
|
8645
|
-
`Failed to read daemon state from ${this.filePath}: ${
|
|
8668
|
+
`Failed to read daemon state from ${this.filePath}: ${extractErrorMessage6(error)}`
|
|
8646
8669
|
);
|
|
8647
8670
|
}
|
|
8648
8671
|
}
|
|
@@ -8661,7 +8684,7 @@ var DaemonStateStore = class {
|
|
|
8661
8684
|
await fs7.unlink(this.filePath);
|
|
8662
8685
|
} catch (error) {
|
|
8663
8686
|
if (!isFileNotFound2(error)) {
|
|
8664
|
-
|
|
8687
|
+
Sentry13.captureException(error);
|
|
8665
8688
|
throw error;
|
|
8666
8689
|
}
|
|
8667
8690
|
}
|
|
@@ -8672,7 +8695,7 @@ function isFileNotFound2(error) {
|
|
|
8672
8695
|
error && typeof error === "object" && "code" in error && error.code === "ENOENT"
|
|
8673
8696
|
);
|
|
8674
8697
|
}
|
|
8675
|
-
function
|
|
8698
|
+
function extractErrorMessage6(error) {
|
|
8676
8699
|
if (error instanceof Error && error.message) {
|
|
8677
8700
|
return error.message;
|
|
8678
8701
|
}
|
|
@@ -8699,7 +8722,7 @@ var DaemonClient = class {
|
|
|
8699
8722
|
await this.stateStore.clear();
|
|
8700
8723
|
return null;
|
|
8701
8724
|
}
|
|
8702
|
-
|
|
8725
|
+
Sentry14.captureException(error);
|
|
8703
8726
|
throw error;
|
|
8704
8727
|
}
|
|
8705
8728
|
}
|
|
@@ -8719,7 +8742,7 @@ var DaemonClient = class {
|
|
|
8719
8742
|
await this.stateStore.clear();
|
|
8720
8743
|
return null;
|
|
8721
8744
|
}
|
|
8722
|
-
|
|
8745
|
+
Sentry14.captureException(error);
|
|
8723
8746
|
throw error;
|
|
8724
8747
|
}
|
|
8725
8748
|
}
|
|
@@ -8742,7 +8765,7 @@ var DaemonClient = class {
|
|
|
8742
8765
|
await this.stateStore.clear();
|
|
8743
8766
|
return false;
|
|
8744
8767
|
}
|
|
8745
|
-
|
|
8768
|
+
Sentry14.captureException(error);
|
|
8746
8769
|
throw error;
|
|
8747
8770
|
}
|
|
8748
8771
|
}
|
|
@@ -8767,7 +8790,7 @@ var DaemonClient = class {
|
|
|
8767
8790
|
await this.stateStore.clear();
|
|
8768
8791
|
return null;
|
|
8769
8792
|
}
|
|
8770
|
-
|
|
8793
|
+
Sentry14.captureException(error);
|
|
8771
8794
|
throw error;
|
|
8772
8795
|
}
|
|
8773
8796
|
}
|
|
@@ -8841,7 +8864,7 @@ function isProcessAlive2(pid) {
|
|
|
8841
8864
|
if (isErrno2(error, "ESRCH")) {
|
|
8842
8865
|
return false;
|
|
8843
8866
|
}
|
|
8844
|
-
|
|
8867
|
+
Sentry14.captureException(error);
|
|
8845
8868
|
return false;
|
|
8846
8869
|
}
|
|
8847
8870
|
}
|
|
@@ -8874,13 +8897,13 @@ var HttpError = class extends Error {
|
|
|
8874
8897
|
// src/daemon/directory-registry.ts
|
|
8875
8898
|
import fs9 from "node:fs/promises";
|
|
8876
8899
|
import path10 from "node:path";
|
|
8877
|
-
import * as
|
|
8900
|
+
import * as Sentry16 from "@sentry/node";
|
|
8878
8901
|
|
|
8879
8902
|
// src/daemon/config-store.ts
|
|
8880
8903
|
import fs8 from "node:fs/promises";
|
|
8881
8904
|
import path9 from "node:path";
|
|
8882
8905
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
8883
|
-
import * as
|
|
8906
|
+
import * as Sentry15 from "@sentry/node";
|
|
8884
8907
|
var DEFAULT_CONFIG = {
|
|
8885
8908
|
directories: [],
|
|
8886
8909
|
settings: {}
|
|
@@ -8898,9 +8921,9 @@ var DaemonConfigStore = class {
|
|
|
8898
8921
|
if (isFileNotFound3(error)) {
|
|
8899
8922
|
return cloneConfig(DEFAULT_CONFIG);
|
|
8900
8923
|
}
|
|
8901
|
-
|
|
8924
|
+
Sentry15.captureException(error);
|
|
8902
8925
|
throw new Error(
|
|
8903
|
-
`Failed to read daemon config from ${this.filePath}: ${
|
|
8926
|
+
`Failed to read daemon config from ${this.filePath}: ${extractErrorMessage7(error)}`
|
|
8904
8927
|
);
|
|
8905
8928
|
}
|
|
8906
8929
|
}
|
|
@@ -8982,7 +9005,7 @@ function isFileNotFound3(error) {
|
|
|
8982
9005
|
error && typeof error === "object" && "code" in error && error.code === "ENOENT"
|
|
8983
9006
|
);
|
|
8984
9007
|
}
|
|
8985
|
-
function
|
|
9008
|
+
function extractErrorMessage7(error) {
|
|
8986
9009
|
if (error instanceof Error && error.message) {
|
|
8987
9010
|
return error.message;
|
|
8988
9011
|
}
|
|
@@ -9020,14 +9043,14 @@ var DirectoryRegistry = class {
|
|
|
9020
9043
|
throw new Error("Path is not a directory");
|
|
9021
9044
|
}
|
|
9022
9045
|
} catch (error) {
|
|
9023
|
-
|
|
9046
|
+
Sentry16.captureException(error);
|
|
9024
9047
|
throw new Error(
|
|
9025
|
-
`Directory ${directoryPath} is not accessible: ${
|
|
9048
|
+
`Directory ${directoryPath} is not accessible: ${extractErrorMessage8(error)}`
|
|
9026
9049
|
);
|
|
9027
9050
|
}
|
|
9028
9051
|
}
|
|
9029
9052
|
};
|
|
9030
|
-
function
|
|
9053
|
+
function extractErrorMessage8(error) {
|
|
9031
9054
|
if (error instanceof Error && error.message) {
|
|
9032
9055
|
return error.message;
|
|
9033
9056
|
}
|
|
@@ -9043,7 +9066,7 @@ import path11 from "node:path";
|
|
|
9043
9066
|
import os3 from "node:os";
|
|
9044
9067
|
import { execFile } from "node:child_process";
|
|
9045
9068
|
import { promisify } from "node:util";
|
|
9046
|
-
import * as
|
|
9069
|
+
import * as Sentry17 from "@sentry/node";
|
|
9047
9070
|
var REGISTRY_PATH = path11.join(OMNARA_HOME_DIR, "session-processes.json");
|
|
9048
9071
|
var LOCK_PATH = `${REGISTRY_PATH}.lock`;
|
|
9049
9072
|
var execFileAsync = promisify(execFile);
|
|
@@ -9055,7 +9078,7 @@ async function acquireLock(retries = 20, delayMs = 50) {
|
|
|
9055
9078
|
return handle;
|
|
9056
9079
|
} catch (error) {
|
|
9057
9080
|
if (error.code !== "EEXIST") {
|
|
9058
|
-
|
|
9081
|
+
Sentry17.captureException(error);
|
|
9059
9082
|
throw error;
|
|
9060
9083
|
}
|
|
9061
9084
|
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
@@ -9092,7 +9115,7 @@ async function readRegistry() {
|
|
|
9092
9115
|
if (error.code === "ENOENT") {
|
|
9093
9116
|
return {};
|
|
9094
9117
|
}
|
|
9095
|
-
|
|
9118
|
+
Sentry17.captureException(error);
|
|
9096
9119
|
throw error;
|
|
9097
9120
|
}
|
|
9098
9121
|
}
|
|
@@ -9120,7 +9143,7 @@ async function isProcessAlive3(pid) {
|
|
|
9120
9143
|
if (error.code === "ESRCH") {
|
|
9121
9144
|
return false;
|
|
9122
9145
|
}
|
|
9123
|
-
|
|
9146
|
+
Sentry17.captureException(error);
|
|
9124
9147
|
return true;
|
|
9125
9148
|
}
|
|
9126
9149
|
}
|
|
@@ -9329,13 +9352,13 @@ async function killAllDaemonProcesses(options = {}) {
|
|
|
9329
9352
|
process.kill(pid, "SIGKILL");
|
|
9330
9353
|
logger3.success(`Killed process ${pid}`);
|
|
9331
9354
|
} catch (error) {
|
|
9332
|
-
logger3.warn(`Failed to kill ${pid}: ${
|
|
9355
|
+
logger3.warn(`Failed to kill ${pid}: ${extractErrorMessage9(error)}`);
|
|
9333
9356
|
}
|
|
9334
9357
|
}
|
|
9335
9358
|
await stateStore.clear();
|
|
9336
9359
|
logger3.debug(`Cleared ${envLabel} daemon state file`);
|
|
9337
9360
|
} catch (error) {
|
|
9338
|
-
logger3.error(`Failed to kill ${envLabel} daemon processes: ${
|
|
9361
|
+
logger3.error(`Failed to kill ${envLabel} daemon processes: ${extractErrorMessage9(error)}`);
|
|
9339
9362
|
}
|
|
9340
9363
|
}
|
|
9341
9364
|
}
|
|
@@ -9352,7 +9375,7 @@ async function killDaemonByState(options = {}) {
|
|
|
9352
9375
|
process.kill(state.pid, "SIGKILL");
|
|
9353
9376
|
logger3.success(`Killed daemon process ${state.pid}`);
|
|
9354
9377
|
} catch (error) {
|
|
9355
|
-
logger3.warn(`Failed to kill daemon ${state.pid}: ${
|
|
9378
|
+
logger3.warn(`Failed to kill daemon ${state.pid}: ${extractErrorMessage9(error)}`);
|
|
9356
9379
|
}
|
|
9357
9380
|
await stateStore.clear();
|
|
9358
9381
|
}
|
|
@@ -9372,13 +9395,13 @@ async function killSessionsByRegistry(options = {}) {
|
|
|
9372
9395
|
process.kill(record.pid, "SIGKILL");
|
|
9373
9396
|
logger3.success(`Killed session process ${record.pid}`);
|
|
9374
9397
|
} catch (error) {
|
|
9375
|
-
logger3.warn(`Failed to kill session ${record.pid}: ${
|
|
9398
|
+
logger3.warn(`Failed to kill session ${record.pid}: ${extractErrorMessage9(error)}`);
|
|
9376
9399
|
}
|
|
9377
9400
|
}
|
|
9378
9401
|
return [{}, void 0];
|
|
9379
9402
|
});
|
|
9380
9403
|
} catch (error) {
|
|
9381
|
-
logger3.warn(`Failed to kill sessions via registry: ${
|
|
9404
|
+
logger3.warn(`Failed to kill sessions via registry: ${extractErrorMessage9(error)}`);
|
|
9382
9405
|
}
|
|
9383
9406
|
}
|
|
9384
9407
|
async function findDaemonRelatedPids() {
|
|
@@ -9415,7 +9438,7 @@ async function registerDirectory(directoryPath, options) {
|
|
|
9415
9438
|
options.logger.warn("Daemon was unavailable; storing directory locally.");
|
|
9416
9439
|
} catch (error) {
|
|
9417
9440
|
options.logger.warn(
|
|
9418
|
-
`Failed to register ${normalized} with daemon: ${
|
|
9441
|
+
`Failed to register ${normalized} with daemon: ${extractErrorMessage9(error)}`
|
|
9419
9442
|
);
|
|
9420
9443
|
}
|
|
9421
9444
|
const registry = new DirectoryRegistry();
|
|
@@ -9453,13 +9476,73 @@ async function waitForReady(client, options = {}) {
|
|
|
9453
9476
|
logger3.warn("Timed out waiting for daemon readiness.");
|
|
9454
9477
|
return null;
|
|
9455
9478
|
}
|
|
9456
|
-
function
|
|
9479
|
+
function extractErrorMessage9(error) {
|
|
9457
9480
|
if (error instanceof Error && error.message) {
|
|
9458
9481
|
return error.message;
|
|
9459
9482
|
}
|
|
9460
9483
|
return String(error);
|
|
9461
9484
|
}
|
|
9462
9485
|
|
|
9486
|
+
// src/commands/auth.ts
|
|
9487
|
+
function buildAuthCommand() {
|
|
9488
|
+
const auth = new Command("auth").description("Authenticate the Omnara CLI.");
|
|
9489
|
+
auth.command("login").description("Authenticate via the browser-based verification flow.").action(async () => {
|
|
9490
|
+
const store = new CredentialsStore();
|
|
9491
|
+
const backendUrl = resolveBackendUrl();
|
|
9492
|
+
try {
|
|
9493
|
+
const configStore = new DaemonConfigStore();
|
|
9494
|
+
await ensureMachineNameConfigured(configStore, logger);
|
|
9495
|
+
await performVerificationLogin({
|
|
9496
|
+
backendUrlOption: backendUrl,
|
|
9497
|
+
frontendUrlOption: resolveFrontendUrl(),
|
|
9498
|
+
store
|
|
9499
|
+
});
|
|
9500
|
+
} catch (error) {
|
|
9501
|
+
Sentry18.captureException(error);
|
|
9502
|
+
if (error instanceof ApiError) {
|
|
9503
|
+
console.error(`
|
|
9504
|
+
Request failed (HTTP ${error.status}): ${error.message}`);
|
|
9505
|
+
} else {
|
|
9506
|
+
console.error(`
|
|
9507
|
+
Login failed: ${extractErrorMessage10(error)}`);
|
|
9508
|
+
}
|
|
9509
|
+
process.exitCode = 1;
|
|
9510
|
+
}
|
|
9511
|
+
});
|
|
9512
|
+
auth.command("logout").description("Remove stored CLI credentials.").action(async () => {
|
|
9513
|
+
const store = new CredentialsStore();
|
|
9514
|
+
await store.clear();
|
|
9515
|
+
console.log("Credentials cleared.");
|
|
9516
|
+
});
|
|
9517
|
+
auth.command("status").description("Show stored CLI credential details.").action(async () => {
|
|
9518
|
+
const store = new CredentialsStore();
|
|
9519
|
+
const credentials = await store.load();
|
|
9520
|
+
if (!credentials) {
|
|
9521
|
+
console.log("No credentials stored.");
|
|
9522
|
+
return;
|
|
9523
|
+
}
|
|
9524
|
+
printStatus(credentials);
|
|
9525
|
+
});
|
|
9526
|
+
return auth;
|
|
9527
|
+
}
|
|
9528
|
+
function printStatus(credentials) {
|
|
9529
|
+
if (credentials.pat) {
|
|
9530
|
+
console.log("Authentication: PAT stored");
|
|
9531
|
+
return;
|
|
9532
|
+
}
|
|
9533
|
+
console.log("Authentication: Not configured");
|
|
9534
|
+
}
|
|
9535
|
+
function extractErrorMessage10(error) {
|
|
9536
|
+
if (error instanceof Error) {
|
|
9537
|
+
return error.message;
|
|
9538
|
+
}
|
|
9539
|
+
return String(error);
|
|
9540
|
+
}
|
|
9541
|
+
|
|
9542
|
+
// src/commands/daemon.ts
|
|
9543
|
+
import { Command as Command2 } from "commander";
|
|
9544
|
+
import * as Sentry24 from "@sentry/node";
|
|
9545
|
+
|
|
9463
9546
|
// src/daemon/run-daemon.ts
|
|
9464
9547
|
import * as Sentry23 from "@sentry/node";
|
|
9465
9548
|
|
|
@@ -9680,7 +9763,7 @@ function extractErrorMessage11(error) {
|
|
|
9680
9763
|
// src/daemon/machine-supervisor.ts
|
|
9681
9764
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
9682
9765
|
import os5 from "node:os";
|
|
9683
|
-
import { setTimeout as
|
|
9766
|
+
import { setTimeout as delay4 } from "node:timers/promises";
|
|
9684
9767
|
import WebSocket2 from "ws";
|
|
9685
9768
|
import * as Sentry21 from "@sentry/node";
|
|
9686
9769
|
|
|
@@ -17681,14 +17764,13 @@ var FALLBACK_NAME = pkg.name ?? "omnara";
|
|
|
17681
17764
|
var FALLBACK_VERSION = pkg.version ?? "0.0.0";
|
|
17682
17765
|
var FALLBACK_DESCRIPTION = pkg.description ?? "Command-line interface for Omnara.";
|
|
17683
17766
|
var PACKAGE_NAME = true ? "omnara" : FALLBACK_NAME;
|
|
17684
|
-
var PACKAGE_VERSION = true ? "0.5.
|
|
17767
|
+
var PACKAGE_VERSION = true ? "0.5.1" : FALLBACK_VERSION;
|
|
17685
17768
|
var PACKAGE_DESCRIPTION = true ? "Command-line interface for Omnara." : FALLBACK_DESCRIPTION;
|
|
17686
17769
|
|
|
17687
17770
|
// src/daemon/machine-supervisor.ts
|
|
17688
17771
|
var HEARTBEAT_INTERVAL_MS = 1e4;
|
|
17689
|
-
var RECONNECT_DELAY_MS = 2e3;
|
|
17690
17772
|
var LOGIN_WAIT_DELAY_MS2 = 3e4;
|
|
17691
|
-
var
|
|
17773
|
+
var CONNECT_TIMEOUT_MS2 = 3e4;
|
|
17692
17774
|
var DAEMON_VERSION = PACKAGE_VERSION ?? "0.0.0";
|
|
17693
17775
|
var MachineSupervisor = class {
|
|
17694
17776
|
backendUrl;
|
|
@@ -17770,11 +17852,13 @@ var MachineSupervisor = class {
|
|
|
17770
17852
|
this.running = false;
|
|
17771
17853
|
}
|
|
17772
17854
|
async runLoop() {
|
|
17855
|
+
let retryDelayMs = INITIAL_RETRY_DELAY_MS;
|
|
17773
17856
|
while (!this.stopRequested) {
|
|
17774
17857
|
try {
|
|
17775
17858
|
const identity = await this.registerMachine();
|
|
17776
17859
|
this.clearLoginWait();
|
|
17777
17860
|
await this.runWebsocket(identity);
|
|
17861
|
+
retryDelayMs = INITIAL_RETRY_DELAY_MS;
|
|
17778
17862
|
} catch (error) {
|
|
17779
17863
|
if (this.stopRequested) {
|
|
17780
17864
|
break;
|
|
@@ -17789,11 +17873,14 @@ var MachineSupervisor = class {
|
|
|
17789
17873
|
} else {
|
|
17790
17874
|
this.logger.debug("Machine supervisor still waiting for login\u2026");
|
|
17791
17875
|
}
|
|
17792
|
-
await
|
|
17876
|
+
await delay4(LOGIN_WAIT_DELAY_MS2);
|
|
17793
17877
|
} else {
|
|
17794
17878
|
Sentry21.captureException(error);
|
|
17795
|
-
this.logger.warn(
|
|
17796
|
-
|
|
17879
|
+
this.logger.warn(
|
|
17880
|
+
`Machine supervisor error: ${error.message}. Retrying in ${Math.round(retryDelayMs / 1e3)}s.`
|
|
17881
|
+
);
|
|
17882
|
+
await delay4(retryDelayMs);
|
|
17883
|
+
retryDelayMs = calculateBackoff(retryDelayMs);
|
|
17797
17884
|
}
|
|
17798
17885
|
}
|
|
17799
17886
|
}
|
|
@@ -17883,9 +17970,13 @@ var MachineSupervisor = class {
|
|
|
17883
17970
|
this.clearHeartbeatTimer();
|
|
17884
17971
|
this.websocket = null;
|
|
17885
17972
|
try {
|
|
17886
|
-
ws.
|
|
17887
|
-
|
|
17973
|
+
if (ws.readyState === WebSocket2.CONNECTING) {
|
|
17974
|
+
ws.terminate();
|
|
17975
|
+
} else if (ws.readyState === WebSocket2.OPEN) {
|
|
17976
|
+
ws.removeAllListeners();
|
|
17888
17977
|
ws.close();
|
|
17978
|
+
} else {
|
|
17979
|
+
ws.removeAllListeners();
|
|
17889
17980
|
}
|
|
17890
17981
|
} catch {
|
|
17891
17982
|
}
|
|
@@ -17897,7 +17988,7 @@ var MachineSupervisor = class {
|
|
|
17897
17988
|
};
|
|
17898
17989
|
connectTimer = setTimeout(() => {
|
|
17899
17990
|
finish(new Error("machine websocket connection timed out"));
|
|
17900
|
-
},
|
|
17991
|
+
}, CONNECT_TIMEOUT_MS2);
|
|
17901
17992
|
ws.on("open", () => {
|
|
17902
17993
|
if (connectTimer) {
|
|
17903
17994
|
clearTimeout(connectTimer);
|
|
@@ -18100,7 +18191,7 @@ var MachineSupervisor = class {
|
|
|
18100
18191
|
return;
|
|
18101
18192
|
}
|
|
18102
18193
|
}
|
|
18103
|
-
await
|
|
18194
|
+
await delay4(250);
|
|
18104
18195
|
}
|
|
18105
18196
|
throw new Error("Machine has not finished registering yet. Try again in a few seconds.");
|
|
18106
18197
|
}
|
|
@@ -18418,6 +18509,8 @@ function buildDaemonCommand() {
|
|
|
18418
18509
|
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
18510
|
const backendUrl = resolveBackendUrl();
|
|
18420
18511
|
const frontendUrl = resolveFrontendUrl();
|
|
18512
|
+
const configStore = new DaemonConfigStore();
|
|
18513
|
+
await ensureMachineNameConfigured(configStore, logger);
|
|
18421
18514
|
await ensureCliCredentials({ backendUrl, frontendUrl, logger });
|
|
18422
18515
|
await startDaemon({
|
|
18423
18516
|
logger,
|
|
@@ -18734,6 +18827,8 @@ async function launchSessionViaDaemon(options = {}) {
|
|
|
18734
18827
|
const backendUrl = resolveBackendUrl(options.backendUrl);
|
|
18735
18828
|
const frontendUrl = resolveFrontendUrl(options.frontendUrl);
|
|
18736
18829
|
try {
|
|
18830
|
+
const configStore = new DaemonConfigStore();
|
|
18831
|
+
await ensureMachineNameConfigured(configStore, logger);
|
|
18737
18832
|
await ensureCliCredentials({ backendUrl, frontendUrl, logger });
|
|
18738
18833
|
await ensureDaemonForSession({
|
|
18739
18834
|
cwd: process.cwd(),
|