omnara 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ const __filename = __omnaraFileURLToPath(import.meta.url);
5
5
  const __dirname = pathShim.dirname(__filename);
6
6
 
7
7
  // src/cli.ts
8
- import path4 from "path";
8
+ import path6 from "path";
9
9
  import { createRequire as createRequire2 } from "node:module";
10
10
  import { Command as Command3 } from "commander";
11
11
 
@@ -63,6 +63,9 @@ var AIClient = class {
63
63
  ...defaultOptions,
64
64
  ...config?.defaults
65
65
  };
66
+ if (this.baseOptions.cwd && this.baseOptions.cwd !== process.cwd()) {
67
+ console.log("[AIClient] Using custom working directory:", this.baseOptions.cwd);
68
+ }
66
69
  this.ensureWorkingDirectory(this.baseOptions.cwd);
67
70
  }
68
71
  async *streamUserMessage(prompt2, overrides) {
@@ -540,14 +543,644 @@ var AsyncQueue = class {
540
543
  }
541
544
  };
542
545
 
546
+ // src/orchestrator/utils/worktree-manager.ts
547
+ import { simpleGit } from "simple-git";
548
+ import * as fs4 from "fs/promises";
549
+ import * as path4 from "path";
550
+
551
+ // src/utils/logger.ts
552
+ import path2 from "path";
553
+ function isProduction() {
554
+ if (process.env.NODE_ENV === "production") {
555
+ return true;
556
+ }
557
+ try {
558
+ const currentFile = typeof __filename !== "undefined" ? __filename : "";
559
+ const isInDist = currentFile.includes(path2.sep + "dist" + path2.sep);
560
+ if (isInDist && process.env.NODE_ENV !== "development") {
561
+ return true;
562
+ }
563
+ } catch {
564
+ }
565
+ const logLevel = process.env.OMNARA_LOG_LEVEL?.toLowerCase();
566
+ if (logLevel === "none" || logLevel === "silent") {
567
+ return true;
568
+ }
569
+ return false;
570
+ }
571
+ function getLogLevel() {
572
+ const envLevel = process.env.OMNARA_LOG_LEVEL?.toLowerCase();
573
+ switch (envLevel) {
574
+ case "none":
575
+ case "silent":
576
+ return 0 /* NONE */;
577
+ case "error":
578
+ return 1 /* ERROR */;
579
+ case "warn":
580
+ case "warning":
581
+ return 2 /* WARN */;
582
+ case "info":
583
+ return 3 /* INFO */;
584
+ case "debug":
585
+ return 4 /* DEBUG */;
586
+ case "verbose":
587
+ case "trace":
588
+ return 5 /* VERBOSE */;
589
+ default:
590
+ return isProduction() ? 1 /* ERROR */ : 3 /* INFO */;
591
+ }
592
+ }
593
+ var ControlledLogger = class {
594
+ logLevel;
595
+ forceOutput;
596
+ _console;
597
+ // Console properties we need to implement
598
+ Console;
599
+ constructor(options) {
600
+ this.logLevel = options?.logLevel ?? getLogLevel();
601
+ this.forceOutput = options?.forceOutput ?? false;
602
+ this._console = console;
603
+ this.Console = console.Console;
604
+ }
605
+ /**
606
+ * Standard log output (INFO level)
607
+ */
608
+ log(...args) {
609
+ if (this.shouldLog(3 /* INFO */)) {
610
+ console.log(...args);
611
+ }
612
+ }
613
+ /**
614
+ * Error output (always shown unless NONE)
615
+ */
616
+ error(...args) {
617
+ if (this.shouldLog(1 /* ERROR */)) {
618
+ console.error(...args);
619
+ }
620
+ }
621
+ /**
622
+ * Warning output
623
+ */
624
+ warn(...args) {
625
+ if (this.shouldLog(2 /* WARN */)) {
626
+ console.warn(...args);
627
+ }
628
+ }
629
+ /**
630
+ * Info level output
631
+ */
632
+ info(...args) {
633
+ if (this.shouldLog(3 /* INFO */)) {
634
+ console.info(...args);
635
+ }
636
+ }
637
+ /**
638
+ * Debug output (only in development)
639
+ */
640
+ debug(...args) {
641
+ if (this.shouldLog(4 /* DEBUG */)) {
642
+ console.debug(...args);
643
+ }
644
+ }
645
+ /**
646
+ * Verbose/trace output
647
+ */
648
+ trace(...args) {
649
+ if (this.shouldLog(5 /* VERBOSE */)) {
650
+ console.log("[TRACE]", ...args);
651
+ }
652
+ }
653
+ /**
654
+ * Important messages that should always be shown to user
655
+ * (authentication status, connection info, etc.)
656
+ */
657
+ important(...args) {
658
+ if (this.logLevel !== 0 /* NONE */ || this.forceOutput) {
659
+ console.log(...args);
660
+ }
661
+ }
662
+ /**
663
+ * Success messages that should typically be shown
664
+ */
665
+ success(...args) {
666
+ if (this.shouldLog(2 /* WARN */) || this.forceOutput) {
667
+ console.log(...args);
668
+ }
669
+ }
670
+ /**
671
+ * Check if a message should be logged at the given level
672
+ */
673
+ shouldLog(level) {
674
+ return this.forceOutput || this.logLevel >= level;
675
+ }
676
+ /**
677
+ * Update the log level dynamically
678
+ */
679
+ setLogLevel(level) {
680
+ this.logLevel = level;
681
+ }
682
+ /**
683
+ * Get current log level
684
+ */
685
+ getLogLevel() {
686
+ return this.logLevel;
687
+ }
688
+ // Console methods we delegate or control based on log level
689
+ assert(condition, ...data) {
690
+ if (this.shouldLog(1 /* ERROR */)) {
691
+ this._console.assert(condition, ...data);
692
+ }
693
+ }
694
+ clear() {
695
+ this._console.clear();
696
+ }
697
+ count(label) {
698
+ if (this.shouldLog(4 /* DEBUG */)) {
699
+ this._console.count(label);
700
+ }
701
+ }
702
+ countReset(label) {
703
+ if (this.shouldLog(4 /* DEBUG */)) {
704
+ this._console.countReset(label);
705
+ }
706
+ }
707
+ dir(item, options) {
708
+ if (this.shouldLog(4 /* DEBUG */)) {
709
+ this._console.dir(item, options);
710
+ }
711
+ }
712
+ dirxml(...data) {
713
+ if (this.shouldLog(4 /* DEBUG */)) {
714
+ this._console.dirxml(...data);
715
+ }
716
+ }
717
+ group(...data) {
718
+ if (this.shouldLog(4 /* DEBUG */)) {
719
+ this._console.group(...data);
720
+ }
721
+ }
722
+ groupCollapsed(...data) {
723
+ if (this.shouldLog(4 /* DEBUG */)) {
724
+ this._console.groupCollapsed(...data);
725
+ }
726
+ }
727
+ groupEnd() {
728
+ if (this.shouldLog(4 /* DEBUG */)) {
729
+ this._console.groupEnd();
730
+ }
731
+ }
732
+ table(tabularData, properties) {
733
+ if (this.shouldLog(3 /* INFO */)) {
734
+ this._console.table(tabularData, properties);
735
+ }
736
+ }
737
+ time(label) {
738
+ if (this.shouldLog(4 /* DEBUG */)) {
739
+ this._console.time(label);
740
+ }
741
+ }
742
+ timeEnd(label) {
743
+ if (this.shouldLog(4 /* DEBUG */)) {
744
+ this._console.timeEnd(label);
745
+ }
746
+ }
747
+ timeLog(label, ...data) {
748
+ if (this.shouldLog(4 /* DEBUG */)) {
749
+ this._console.timeLog(label, ...data);
750
+ }
751
+ }
752
+ timeStamp(label) {
753
+ if (this.shouldLog(4 /* DEBUG */)) {
754
+ this._console.timeStamp(label);
755
+ }
756
+ }
757
+ profile(label) {
758
+ if (this.shouldLog(4 /* DEBUG */)) {
759
+ const consoleWithProfile = this._console;
760
+ consoleWithProfile.profile?.(label);
761
+ }
762
+ }
763
+ profileEnd(label) {
764
+ if (this.shouldLog(4 /* DEBUG */)) {
765
+ const consoleWithProfileEnd = this._console;
766
+ consoleWithProfileEnd.profileEnd?.(label);
767
+ }
768
+ }
769
+ };
770
+ var logger = new ControlledLogger();
771
+ function extractErrorMessage2(error) {
772
+ if (error instanceof Error) {
773
+ return error.message;
774
+ }
775
+ return String(error);
776
+ }
777
+
778
+ // src/auth/credentials-store.ts
779
+ import fs3 from "fs/promises";
780
+ import os2 from "os";
781
+ import path3 from "path";
782
+ var DEFAULT_DIR = path3.join(os2.homedir(), ".omnara");
783
+ var DEFAULT_CREDENTIALS_PATH = path3.join(DEFAULT_DIR, "creds.json");
784
+ var DEFAULT_FILE_MODE = 384;
785
+ var DEFAULT_DIR_MODE = 448;
786
+ function isStoredCredentials(value) {
787
+ return Boolean(value) && typeof value === "object";
788
+ }
789
+ var CredentialsStore = class {
790
+ constructor(filePath = DEFAULT_CREDENTIALS_PATH) {
791
+ this.filePath = filePath;
792
+ }
793
+ async load() {
794
+ try {
795
+ const raw = await fs3.readFile(this.filePath, "utf8");
796
+ const parsed = JSON.parse(raw);
797
+ if (!isStoredCredentials(parsed)) {
798
+ throw new Error("File does not contain valid credentials data");
799
+ }
800
+ return parsed;
801
+ } catch (error) {
802
+ if (isFileNotFound(error)) {
803
+ return null;
804
+ }
805
+ throw new Error(
806
+ `Failed to read credentials from ${this.filePath}: ${extractErrorMessage3(error)}`
807
+ );
808
+ }
809
+ }
810
+ async save(credentials) {
811
+ const payload = {
812
+ accessToken: credentials.accessToken,
813
+ accessTokenExpiresAt: credentials.accessTokenExpiresAt,
814
+ refreshToken: credentials.refreshToken,
815
+ refreshTokenExpiresAt: credentials.refreshTokenExpiresAt
816
+ };
817
+ await fs3.mkdir(path3.dirname(this.filePath), { recursive: true, mode: DEFAULT_DIR_MODE });
818
+ const tempPath = `${this.filePath}.tmp`;
819
+ const serialized = JSON.stringify(payload, null, 2);
820
+ await fs3.writeFile(tempPath, serialized, { mode: DEFAULT_FILE_MODE });
821
+ await fs3.chmod(tempPath, DEFAULT_FILE_MODE);
822
+ await fs3.rename(tempPath, this.filePath);
823
+ }
824
+ async clear() {
825
+ try {
826
+ await fs3.unlink(this.filePath);
827
+ } catch (error) {
828
+ if (!isFileNotFound(error)) {
829
+ throw new Error(
830
+ `Failed to remove credentials file ${this.filePath}: ${extractErrorMessage3(error)}`
831
+ );
832
+ }
833
+ }
834
+ }
835
+ };
836
+ function isFileNotFound(error) {
837
+ return Boolean(
838
+ error && typeof error === "object" && "code" in error && error.code === "ENOENT"
839
+ );
840
+ }
841
+ function extractErrorMessage3(error) {
842
+ if (error instanceof Error && error.message) {
843
+ return error.message;
844
+ }
845
+ return String(error);
846
+ }
847
+
848
+ // src/orchestrator/utils/worktree-manager.ts
849
+ var WorktreeError = class extends Error {
850
+ constructor(message, workerId, operation, cause) {
851
+ super(message);
852
+ this.workerId = workerId;
853
+ this.operation = operation;
854
+ this.cause = cause;
855
+ this.name = "WorktreeError";
856
+ }
857
+ };
858
+ var WorktreeManager = class {
859
+ git;
860
+ repoPath;
861
+ worktreeBasePath;
862
+ activeWorktrees;
863
+ enabled;
864
+ constructor(options) {
865
+ this.repoPath = options.repoPath;
866
+ this.enabled = options.enabled ?? false;
867
+ const repoName = path4.basename(this.repoPath);
868
+ this.worktreeBasePath = options.worktreeBasePath || path4.join(DEFAULT_DIR, "worktrees", repoName);
869
+ this.git = simpleGit(this.repoPath);
870
+ this.activeWorktrees = /* @__PURE__ */ new Map();
871
+ if (this.enabled) {
872
+ this.validateGitRepository();
873
+ }
874
+ }
875
+ /**
876
+ * Validate that the repoPath is a git repository
877
+ * Logs an error if worktrees are enabled but not in a git repository
878
+ */
879
+ validateGitRepository() {
880
+ this.git.checkIsRepo().then((isRepo) => {
881
+ if (!isRepo) {
882
+ logger.error(`Worktrees are enabled but not in a git repository at ${this.repoPath}`);
883
+ } else {
884
+ logger.info(`Worktrees enabled for repository at ${this.repoPath}`);
885
+ }
886
+ }).catch((error) => {
887
+ logger.error(
888
+ `Worktrees are enabled but failed to check git repository at ${this.repoPath}:`,
889
+ error
890
+ );
891
+ });
892
+ }
893
+ /**
894
+ * Check if worktrees are enabled
895
+ */
896
+ isEnabled() {
897
+ return this.enabled;
898
+ }
899
+ /**
900
+ * Creates a new worktree for the specified worker
901
+ *
902
+ * @param workerId - Unique identifier for the worker
903
+ * @param worktreeName - Semantic name for the worktree (e.g., "main-feature", "bugfix-auth")
904
+ * @returns Information about the created worktree
905
+ * @throws {WorktreeError} If worktree creation fails
906
+ */
907
+ async createWorktree(workerId, worktreeName) {
908
+ if (!this.enabled) {
909
+ throw new WorktreeError(
910
+ 'Worktrees are not enabled. Add a "worktrees" configuration to omnara.jsonc to enable.',
911
+ workerId,
912
+ "create"
913
+ );
914
+ }
915
+ logger.info(`Creating worktree "${worktreeName}" for worker ${workerId}`);
916
+ try {
917
+ if (this.activeWorktrees.has(workerId)) {
918
+ throw new WorktreeError(
919
+ `Worktree already exists for worker ${workerId}`,
920
+ workerId,
921
+ "create"
922
+ );
923
+ }
924
+ await fs4.mkdir(this.worktreeBasePath, { recursive: true });
925
+ const branchName = `${worktreeName}`;
926
+ const worktreePath = path4.join(this.worktreeBasePath, worktreeName);
927
+ const branches = await this.git.branchLocal();
928
+ const branchExists = branches.all.includes(branchName);
929
+ let worktreeDirExists = false;
930
+ try {
931
+ await fs4.access(worktreePath);
932
+ worktreeDirExists = true;
933
+ } catch {
934
+ worktreeDirExists = false;
935
+ }
936
+ const worktreeList = await this.git.raw(["worktree", "list", "--porcelain"]);
937
+ const gitWorktrees = this.parseWorktreeList(worktreeList);
938
+ const worktreeRegistered = gitWorktrees.includes(worktreePath);
939
+ if (worktreeRegistered && branchExists) {
940
+ logger.info(
941
+ `Reusing existing worktree "${worktreeName}" at ${worktreePath} on branch ${branchName}`
942
+ );
943
+ const info2 = {
944
+ workerId,
945
+ worktreeName,
946
+ worktreePath,
947
+ branchName,
948
+ createdAt: /* @__PURE__ */ new Date()
949
+ };
950
+ this.activeWorktrees.set(workerId, info2);
951
+ return info2;
952
+ }
953
+ if (worktreeDirExists && !worktreeRegistered) {
954
+ logger.warn(
955
+ `Directory exists at ${worktreePath} but is not a registered worktree. Removing...`
956
+ );
957
+ await fs4.rm(worktreePath, { recursive: true, force: true });
958
+ worktreeDirExists = false;
959
+ }
960
+ if (branchExists && !worktreeRegistered) {
961
+ logger.info(
962
+ `Branch ${branchName} exists. Creating worktree at ${worktreePath} using existing branch`
963
+ );
964
+ await this.git.raw(["worktree", "add", worktreePath, branchName]);
965
+ } else if (!branchExists && !worktreeRegistered) {
966
+ logger.info(
967
+ `Creating new worktree "${worktreeName}" at ${worktreePath} with new branch ${branchName}`
968
+ );
969
+ await this.git.raw(["worktree", "add", "-b", branchName, worktreePath]);
970
+ }
971
+ const info = {
972
+ workerId,
973
+ worktreeName,
974
+ worktreePath,
975
+ branchName,
976
+ createdAt: /* @__PURE__ */ new Date()
977
+ };
978
+ this.activeWorktrees.set(workerId, info);
979
+ logger.info(`Worktree "${worktreeName}" ready at ${worktreePath} on branch ${branchName}`);
980
+ return info;
981
+ } catch (error) {
982
+ logger.error(`Failed to create worktree for worker ${workerId}:`, error);
983
+ throw new WorktreeError(
984
+ `Failed to create worktree: ${error instanceof Error ? error.message : String(error)}`,
985
+ workerId,
986
+ "create",
987
+ error instanceof Error ? error : void 0
988
+ );
989
+ }
990
+ }
991
+ /**
992
+ * Removes a worktree for the specified worker
993
+ *
994
+ * @param workerId - Unique identifier for the worker
995
+ * @param force - Force removal even if there are uncommitted changes
996
+ * @throws {WorktreeError} If worktree removal fails
997
+ */
998
+ async removeWorktree(workerId, force = false) {
999
+ if (!this.enabled) {
1000
+ return;
1001
+ }
1002
+ logger.info(`Removing worktree for worker ${workerId}`);
1003
+ const info = this.activeWorktrees.get(workerId);
1004
+ if (!info) {
1005
+ logger.warn(`No active worktree found for worker ${workerId}`);
1006
+ return;
1007
+ }
1008
+ try {
1009
+ const args = ["worktree", "remove", info.worktreePath];
1010
+ if (force) {
1011
+ args.push("--force");
1012
+ }
1013
+ try {
1014
+ await this.git.raw(args);
1015
+ } catch (worktreeError) {
1016
+ const errorMessage = worktreeError instanceof Error ? worktreeError.message : String(worktreeError);
1017
+ if (errorMessage.includes("is not a working tree") || errorMessage.includes("does not exist")) {
1018
+ logger.warn(
1019
+ `Worktree ${info.worktreePath} doesn't exist, may have been manually removed. Cleaning up...`
1020
+ );
1021
+ await this.git.raw(["worktree", "prune"]);
1022
+ try {
1023
+ await fs4.rm(info.worktreePath, { recursive: true, force: true });
1024
+ logger.info(`Manually removed stale worktree directory: ${info.worktreePath}`);
1025
+ } catch {
1026
+ }
1027
+ } else {
1028
+ throw worktreeError;
1029
+ }
1030
+ }
1031
+ this.activeWorktrees.delete(workerId);
1032
+ logger.info(`Removed worktree for worker ${workerId}`);
1033
+ } catch (error) {
1034
+ logger.error(`Failed to remove worktree for worker ${workerId}:`, error);
1035
+ throw new WorktreeError(
1036
+ `Failed to remove worktree: ${error instanceof Error ? error.message : String(error)}`,
1037
+ workerId,
1038
+ "remove",
1039
+ error instanceof Error ? error : void 0
1040
+ );
1041
+ }
1042
+ }
1043
+ /**
1044
+ * Gets information about a worker's worktree
1045
+ *
1046
+ * @param workerId - Unique identifier for the worker
1047
+ * @returns Worktree information or undefined if not found
1048
+ */
1049
+ getWorktreeInfo(workerId) {
1050
+ return this.activeWorktrees.get(workerId);
1051
+ }
1052
+ /**
1053
+ * Lists all active worktrees managed by this instance
1054
+ *
1055
+ * @returns Array of worktree information
1056
+ */
1057
+ listActiveWorktrees() {
1058
+ return Array.from(this.activeWorktrees.values());
1059
+ }
1060
+ /**
1061
+ * Cleans up all active worktrees
1062
+ *
1063
+ * This method should be called during application shutdown to ensure
1064
+ * all worktrees are properly cleaned up.
1065
+ *
1066
+ * @param force - Force removal even if there are uncommitted changes
1067
+ */
1068
+ async cleanupAll(force = false) {
1069
+ if (!this.enabled) {
1070
+ return;
1071
+ }
1072
+ logger.info("Cleaning up all worktrees");
1073
+ const workers = Array.from(this.activeWorktrees.keys());
1074
+ const results = await Promise.allSettled(
1075
+ workers.map((workerId) => this.removeWorktree(workerId, force))
1076
+ );
1077
+ const failures = results.filter((r) => r.status === "rejected");
1078
+ if (failures.length > 0) {
1079
+ logger.error(`Failed to cleanup ${failures.length} worktrees`);
1080
+ } else {
1081
+ logger.info("Successfully cleaned up all worktrees");
1082
+ }
1083
+ }
1084
+ /**
1085
+ * Parses the output of 'git worktree list --porcelain'
1086
+ *
1087
+ * @param output - Raw output from git worktree list
1088
+ * @returns Array of worktree paths
1089
+ */
1090
+ parseWorktreeList(output2) {
1091
+ const lines = output2.split("\n");
1092
+ const paths = [];
1093
+ for (const line of lines) {
1094
+ if (line.startsWith("worktree ")) {
1095
+ paths.push(line.substring("worktree ".length));
1096
+ }
1097
+ }
1098
+ return paths;
1099
+ }
1100
+ /**
1101
+ * Checks if the repository is in a clean state for worktree operations
1102
+ *
1103
+ * @returns True if repository is clean
1104
+ */
1105
+ async isRepositoryClean() {
1106
+ const status = await this.git.status();
1107
+ return status.files.length === 0;
1108
+ }
1109
+ };
1110
+
1111
+ // src/config/omnara-config.ts
1112
+ import { parse as parseJsonc } from "jsonc-parser";
1113
+ import * as fs5 from "fs/promises";
1114
+ import * as path5 from "path";
1115
+ import * as z2 from "zod";
1116
+ var OmnaraConfig = z2.object({
1117
+ worktrees: z2.object({
1118
+ enabled: z2.boolean().default(false)
1119
+ }).default({
1120
+ enabled: false
1121
+ })
1122
+ });
1123
+ async function readOmnaraConfig(configPath) {
1124
+ const resolveConfig = async (path7) => {
1125
+ if (!path7) {
1126
+ return null;
1127
+ }
1128
+ await fs5.access(path7);
1129
+ const content = await fs5.readFile(path7, "utf-8");
1130
+ const errors = [];
1131
+ const config = parseJsonc(content, errors, {
1132
+ allowTrailingComma: true,
1133
+ allowEmptyContent: true
1134
+ });
1135
+ return OmnaraConfig.parse(config);
1136
+ };
1137
+ const resolveDefault = () => {
1138
+ return OmnaraConfig.parse(void 0);
1139
+ };
1140
+ return await resolveConfig(configPath).catch(() => null) ?? await resolveConfig(path5.join(process.cwd(), "omnara.jsonc")).catch(() => null) ?? await resolveConfig(path5.join(process.cwd(), "omnara.json")).catch(() => null) ?? await resolveDefault();
1141
+ }
1142
+ function isWorktreesEnabled(config) {
1143
+ return config.worktrees.enabled;
1144
+ }
1145
+
543
1146
  // src/orchestrator/worker-manager.ts
544
1147
  var WorkerManager = class extends EventEmitter {
545
1148
  constructor(options) {
546
1149
  super();
547
1150
  this.options = options;
1151
+ this.worktreeManager = new WorktreeManager({
1152
+ repoPath: options.repoPath,
1153
+ enabled: false
1154
+ });
1155
+ this.initializeConfig();
1156
+ }
1157
+ workers = /* @__PURE__ */ new Map();
1158
+ runningQueries = /* @__PURE__ */ new Map();
1159
+ worktreeManager;
1160
+ configInitialized = false;
1161
+ /**
1162
+ * Initialize the configuration and update WorktreeManager accordingly
1163
+ */
1164
+ async initializeConfig() {
1165
+ try {
1166
+ const config = await readOmnaraConfig();
1167
+ const worktreesEnabled = isWorktreesEnabled(config);
1168
+ if (worktreesEnabled) {
1169
+ logger.info("Worktrees enabled via omnara.jsonc configuration");
1170
+ } else {
1171
+ logger.info("Worktrees disabled (no worktrees config found in omnara.jsonc)");
1172
+ }
1173
+ this.worktreeManager = new WorktreeManager({
1174
+ repoPath: this.options.repoPath,
1175
+ enabled: worktreesEnabled
1176
+ });
1177
+ this.configInitialized = true;
1178
+ } catch (err) {
1179
+ logger.error("Failed to read omnara.jsonc configuration:", err);
1180
+ logger.info("Worktrees disabled due to config read error");
1181
+ this.configInitialized = true;
1182
+ }
548
1183
  }
549
- workers = /* @__PURE__ */ new Map();
550
- runningQueries = /* @__PURE__ */ new Map();
551
1184
  listWorkers() {
552
1185
  return Array.from(this.workers.values());
553
1186
  }
@@ -589,7 +1222,7 @@ var WorkerManager = class extends EventEmitter {
589
1222
  }
590
1223
  return lines.join("\n");
591
1224
  }
592
- launchWorker({ task, prompt: prompt2, originSessionId, metadata }) {
1225
+ launchWorker({ task, prompt: prompt2, work_context, worktree_name, originSessionId, metadata }, toolUseId) {
593
1226
  const sessionId = randomUUID2();
594
1227
  const resolvedOriginSessionId = originSessionId;
595
1228
  const record = {
@@ -604,14 +1237,81 @@ var WorkerManager = class extends EventEmitter {
604
1237
  originSessionId: resolvedOriginSessionId,
605
1238
  progress: [],
606
1239
  metadata,
607
- messageQueue: new AsyncQueue()
1240
+ messageQueue: new AsyncQueue(),
1241
+ work_context,
1242
+ worktree_name
608
1243
  };
609
1244
  this.workers.set(sessionId, record);
610
1245
  this.emit("session_start", { ...record });
611
1246
  this.emitUpdate(record);
612
- void this.runWorker(record, prompt2);
1247
+ if (toolUseId) {
1248
+ this.emit("message", {
1249
+ record,
1250
+ messageType: "tool_call",
1251
+ toolName: "mcp__worker__launch_worker",
1252
+ toolUseId,
1253
+ toolInput: { task, prompt: prompt2, metadata }
1254
+ });
1255
+ this.emit("message", {
1256
+ record,
1257
+ messageType: "tool_result",
1258
+ toolUseId,
1259
+ toolContent: {
1260
+ workerId: record.id,
1261
+ status: record.status,
1262
+ message: `Worker launched successfully, task ${task} in progress.`
1263
+ },
1264
+ isError: false
1265
+ });
1266
+ }
1267
+ void this.createWorktreeAndRun(record, prompt2);
613
1268
  return record;
614
1269
  }
1270
+ async createWorktreeAndRun(record, prompt2) {
1271
+ try {
1272
+ if (this.worktreeManager.isEnabled()) {
1273
+ logger.info(`Creating worktree for worker ${record.id} (task: ${record.task})`);
1274
+ const worktreeInfo = await this.worktreeManager.createWorktree(
1275
+ record.id,
1276
+ record.worktree_name || record.id
1277
+ );
1278
+ record.worktreePath = worktreeInfo.worktreePath;
1279
+ record.branchName = worktreeInfo.branchName;
1280
+ record.progress.push({
1281
+ timestamp: Date.now(),
1282
+ message: `Worktree created at ${worktreeInfo.worktreePath} on branch ${worktreeInfo.branchName}`
1283
+ });
1284
+ this.emitUpdate(record);
1285
+ logger.info(`Worktree created for worker ${record.id} at ${worktreeInfo.worktreePath}`);
1286
+ } else {
1287
+ logger.info(`Worktrees disabled, worker ${record.id} will run in current directory`);
1288
+ record.progress.push({
1289
+ timestamp: Date.now(),
1290
+ message: "Worktrees disabled, running in current directory"
1291
+ });
1292
+ this.emitUpdate(record);
1293
+ }
1294
+ await this.runWorker(record, prompt2);
1295
+ } catch (error) {
1296
+ logger.error(`Failed to setup worker ${record.id}:`, error);
1297
+ record.status = "error";
1298
+ record.error = `Failed to setup worker: ${error instanceof Error ? error.message : String(error)}`;
1299
+ record.lastUpdateAt = Date.now();
1300
+ record.progress.push({
1301
+ timestamp: Date.now(),
1302
+ message: `Worker failed: ${record.error}`
1303
+ });
1304
+ this.emitUpdate(record);
1305
+ this.emit("message", {
1306
+ record,
1307
+ messageType: "assistant_complete",
1308
+ success: false,
1309
+ text: record.error
1310
+ });
1311
+ this.emitCompleted(record);
1312
+ record.messageQueue.close();
1313
+ }
1314
+ }
615
1315
  cancelWorker(id) {
616
1316
  const record = this.workers.get(id);
617
1317
  if (!record) {
@@ -642,19 +1342,41 @@ var WorkerManager = class extends EventEmitter {
642
1342
  return true;
643
1343
  }
644
1344
  /**
645
- * Send a message to a running worker.
1345
+ * Send a message to a worker (running or completed).
646
1346
  *
647
1347
  * @param task - The task name of the worker to message
648
1348
  * @param message - The message content to send
649
1349
  * @param interrupt - If true, interrupt current generation; if false, queue for next turn
650
- * @returns true if message was sent, false if worker not found or not running
1350
+ * @param toolUseId - Optional tool use ID to track this operation for relay forwarding
1351
+ * @returns true if message was sent, false if worker not found or message queue is closed
651
1352
  */
652
- async messageWorker(task, message, interrupt) {
1353
+ async messageWorker(task, message, interrupt, toolUseId) {
653
1354
  const record = this.getWorkerByTask(task);
654
1355
  if (!record) {
655
1356
  return false;
656
1357
  }
1358
+ if (toolUseId) {
1359
+ this.emit("message", {
1360
+ record,
1361
+ messageType: "tool_call",
1362
+ toolName: "mcp__worker__message_worker",
1363
+ toolUseId,
1364
+ toolInput: { task, message, interrupt }
1365
+ });
1366
+ }
657
1367
  if (record.status !== "running") {
1368
+ record.messageQueue = new AsyncQueue();
1369
+ record.status = "running";
1370
+ record.lastUpdateAt = Date.now();
1371
+ record.progress.push({
1372
+ timestamp: Date.now(),
1373
+ message: `Worker restarted with new message: ${message}`
1374
+ });
1375
+ this.emitUpdate(record);
1376
+ void this.runWorker(record, "");
1377
+ }
1378
+ if (record.messageQueue.isClosed()) {
1379
+ console.error("[WorkerManager] Message queue is closed after restart attempt");
658
1380
  return false;
659
1381
  }
660
1382
  record.progress.push({
@@ -684,9 +1406,35 @@ var WorkerManager = class extends EventEmitter {
684
1406
  };
685
1407
  try {
686
1408
  record.messageQueue.push(userMessage);
1409
+ if (toolUseId) {
1410
+ this.emit("message", {
1411
+ record,
1412
+ messageType: "tool_result",
1413
+ toolUseId,
1414
+ toolContent: {
1415
+ success: true,
1416
+ task,
1417
+ interrupted: interrupt,
1418
+ message: interrupt ? `Message sent to worker "${task}" (interrupted current work)` : `Message queued for worker "${task}" (will be delivered after current turn)`
1419
+ },
1420
+ isError: false
1421
+ });
1422
+ }
687
1423
  return true;
688
1424
  } catch (error) {
689
1425
  console.error("[WorkerManager] Failed to queue message:", error);
1426
+ if (toolUseId) {
1427
+ this.emit("message", {
1428
+ record,
1429
+ messageType: "tool_result",
1430
+ toolUseId,
1431
+ toolContent: {
1432
+ success: false,
1433
+ error: error instanceof Error ? error.message : "Failed to queue message"
1434
+ },
1435
+ isError: true
1436
+ });
1437
+ }
690
1438
  return false;
691
1439
  }
692
1440
  }
@@ -713,17 +1461,20 @@ var WorkerManager = class extends EventEmitter {
713
1461
  /**
714
1462
  * Create an async iterable stream of user messages for the worker.
715
1463
  * Yields the initial prompt, then streams any additional messages from the queue.
1464
+ * If initialPrompt is empty, skips the initial yield and only consumes from the queue.
716
1465
  */
717
1466
  async *createMessageStream(initialPrompt, sessionId, messageQueue) {
718
- yield {
719
- session_id: sessionId,
720
- type: "user",
721
- message: {
722
- role: "user",
723
- content: initialPrompt
724
- },
725
- parent_tool_use_id: null
726
- };
1467
+ if (initialPrompt.trim()) {
1468
+ yield {
1469
+ session_id: sessionId,
1470
+ type: "user",
1471
+ message: {
1472
+ role: "user",
1473
+ content: initialPrompt
1474
+ },
1475
+ parent_tool_use_id: null
1476
+ };
1477
+ }
727
1478
  for await (const message of messageQueue) {
728
1479
  yield message;
729
1480
  }
@@ -735,12 +1486,17 @@ var WorkerManager = class extends EventEmitter {
735
1486
  console.log("[WorkerManager] Creating worker client with status MCP server:", {
736
1487
  workerId: record.id,
737
1488
  task: record.task,
738
- hasMcpServer: !!statusMcpServer
739
- });
740
- const client = this.options.createClient({
741
- worker_status: statusMcpServer
1489
+ hasMcpServer: !!statusMcpServer,
1490
+ worktreePath: record.worktreePath,
1491
+ branchName: record.branchName
742
1492
  });
743
- const workerPrompt = this.wrapPrompt(prompt2);
1493
+ const client = this.options.createClient(
1494
+ {
1495
+ worker_status: statusMcpServer
1496
+ },
1497
+ record.worktreePath
1498
+ );
1499
+ const workerPrompt = this.wrapPrompt(prompt2, record.worktreePath, record.branchName);
744
1500
  const messageStream = this.createMessageStream(
745
1501
  workerPrompt,
746
1502
  record.sessionId,
@@ -827,7 +1583,6 @@ var WorkerManager = class extends EventEmitter {
827
1583
  });
828
1584
  this.emitCompleted(record);
829
1585
  } finally {
830
- record.messageQueue.close();
831
1586
  this.runningQueries.delete(record.id);
832
1587
  }
833
1588
  }
@@ -960,22 +1715,58 @@ var WorkerManager = class extends EventEmitter {
960
1715
  return "Unknown worker error";
961
1716
  }
962
1717
  }
963
- wrapPrompt(userPrompt) {
964
- const directive = `
1718
+ wrapPrompt(userPrompt, worktreePath, branchName) {
1719
+ let directive = `
965
1720
 
966
1721
  IMPORTANT:
967
1722
  - Use the mcp__worker_status__send_status_update tool to report progress as you work.
968
1723
  - When finished, output a final summary.
969
1724
  - Do not include raw diffs or large file dumps unless explicitly asked.
970
1725
  `;
1726
+ if (worktreePath && branchName) {
1727
+ directive += `
1728
+ WORKTREE ISOLATION:
1729
+ - You are working in an isolated git worktree at: ${worktreePath}
1730
+ - Your dedicated branch is: ${branchName}
1731
+ - All your file operations, git commands, and changes are isolated to this worktree.
1732
+ - Other workers cannot see your changes and you cannot see theirs.
1733
+ - When you're done, your worktree will be automatically cleaned up.
1734
+
1735
+ COMMIT YOUR CHANGES:
1736
+ - Before completing your task, commit your changes using git.
1737
+ - Use a descriptive commit message that explains what you did.
1738
+ - Your commits will stay on your isolated branch and can be reviewed before merging.
1739
+ - This ensures your work is preserved even if cleanup happens unexpectedly.
1740
+ `;
1741
+ }
971
1742
  return `${userPrompt.trim()}
972
1743
  ${directive}`;
973
1744
  }
1745
+ /**
1746
+ * Shutdown the worker manager and cleanup all resources
1747
+ * This should be called during application shutdown to ensure all worktrees are cleaned up
1748
+ */
1749
+ async shutdown() {
1750
+ logger.info("Shutting down worker manager");
1751
+ const runningWorkers = Array.from(this.workers.values()).filter((w) => w.status === "running");
1752
+ for (const worker of runningWorkers) {
1753
+ logger.info(`Canceling worker ${worker.id} during shutdown`);
1754
+ this.cancelWorker(worker.id);
1755
+ }
1756
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
1757
+ try {
1758
+ await this.worktreeManager.cleanupAll(true);
1759
+ logger.info("All worktrees cleaned up successfully");
1760
+ } catch (error) {
1761
+ logger.error("Error during worktree cleanup:", error);
1762
+ }
1763
+ logger.info("Worker manager shutdown complete");
1764
+ }
974
1765
  };
975
1766
 
976
1767
  // src/orchestrator/tools/worker-tools.ts
977
1768
  import { createSdkMcpServer as createSdkMcpServer2, tool as tool2 } from "@anthropic-ai/claude-agent-sdk";
978
- import { z as z2 } from "zod";
1769
+ import { z as z3 } from "zod";
979
1770
  function createWorkerMcpServer(workerManager, context) {
980
1771
  return createSdkMcpServer2({
981
1772
  name: "worker",
@@ -983,7 +1774,13 @@ function createWorkerMcpServer(workerManager, context) {
983
1774
  tools: [
984
1775
  tool2(
985
1776
  "launch_worker",
986
- `Launch a background worker agent to handle tasks autonomously.
1777
+ `Launch a background worker agent to handle tasks autonomously in an isolated git worktree.
1778
+
1779
+ Each worker operates in its own git worktree, providing complete isolation:
1780
+ - Separate working directory (no shared file changes)
1781
+ - Dedicated git branch
1782
+ - Independent git operations (staging, committing, etc.)
1783
+ - The worker's worktree is automatically cleaned up when the worker completes
987
1784
 
988
1785
  When to use this tool:
989
1786
  - Complex tasks requiring multiple steps or operations
@@ -999,11 +1796,14 @@ Usage notes:
999
1796
  - The worker's result is returned to you, not the user - you must summarize or forward relevant information
1000
1797
  - Worker progress status is automatically included in your context - you can see their current state without additional tools`,
1001
1798
  {
1002
- task: z2.string().min(1, "task name is required").describe(
1799
+ task: z3.string().min(1, "task name is required").describe(
1003
1800
  'A short, unique name for this task (e.g., "FizzBuzz Python", "Search codebase", "Fix bug #123"). Used to identify and track this worker.'
1004
1801
  ),
1005
- prompt: z2.string().min(1, "prompt is required"),
1006
- metadata: z2.record(z2.any()).optional()
1802
+ prompt: z3.string().min(1, "prompt is required"),
1803
+ worktree_name: z3.string().min(1, "worktree_name is required").describe(
1804
+ "A short descriptive name for a worktree to create that can be used by workers simultaneously. This is synonymous with branch."
1805
+ ),
1806
+ metadata: z3.record(z3.any()).optional()
1007
1807
  },
1008
1808
  async (args, extra) => {
1009
1809
  const typedExtra = extra;
@@ -1024,14 +1824,20 @@ Usage notes:
1024
1824
  console.log("[WorkerTools] Launching worker:", {
1025
1825
  task: args.task,
1026
1826
  parentSessionId,
1027
- mainCodingSession: context.getMainCodingSessionId()
1028
- });
1029
- const record = workerManager.launchWorker({
1030
- task: args.task,
1031
- prompt: args.prompt,
1032
- originSessionId: parentSessionId,
1033
- metadata: args.metadata
1827
+ mainCodingSession: context.getMainCodingSessionId(),
1828
+ toolUseId
1034
1829
  });
1830
+ const record = workerManager.launchWorker(
1831
+ {
1832
+ task: args.task,
1833
+ prompt: args.prompt,
1834
+ work_context: args.worktree_name,
1835
+ worktree_name: args.worktree_name,
1836
+ originSessionId: parentSessionId,
1837
+ metadata: args.metadata
1838
+ },
1839
+ toolUseId
1840
+ );
1035
1841
  return {
1036
1842
  content: [
1037
1843
  {
@@ -1039,6 +1845,8 @@ Usage notes:
1039
1845
  text: JSON.stringify({
1040
1846
  workerId: record.id,
1041
1847
  status: record.status,
1848
+ worktree_path: record.worktreePath,
1849
+ branch_name: record.branchName,
1042
1850
  message: `Worker launched successfully, task ${args.task} in progress. You will be notified for progress updates.`
1043
1851
  })
1044
1852
  }
@@ -1093,19 +1901,29 @@ Orchestrator: [launches worker: "refactor database"]
1093
1901
  User: "Wait, actually don't touch the database - just refactor the API layer instead"
1094
1902
  Orchestrator: [message_worker: task="refactor database", message="User wants to change scope - refactor API layer instead, leave database alone", interrupt=true]`,
1095
1903
  {
1096
- task: z2.string().min(1, "task name is required").describe("The task name of the worker to message"),
1097
- message: z2.string().min(1, "message is required").describe(
1904
+ task: z3.string().min(1, "task name is required").describe("The task name of the worker to message"),
1905
+ message: z3.string().min(1, "message is required").describe(
1098
1906
  "The message to send to the worker. Be clear and specific about what has changed or what additional context is being provided."
1099
1907
  ),
1100
- interrupt: z2.boolean().default(false).describe(
1908
+ interrupt: z3.boolean().default(false).describe(
1101
1909
  "If true, interrupt current generation immediately; if false (default), queue message for next turn"
1102
1910
  )
1103
1911
  },
1104
- async (args) => {
1912
+ async (args, extra) => {
1913
+ const typedExtra = extra;
1914
+ const toolUseIdRaw = typedExtra?._meta?.["claudecode/toolUseId"];
1915
+ const toolUseId = typeof toolUseIdRaw === "string" ? toolUseIdRaw : void 0;
1916
+ console.log("[WorkerTools] Messaging worker:", {
1917
+ task: args.task,
1918
+ message: args.message.substring(0, 100),
1919
+ interrupt: args.interrupt,
1920
+ toolUseId
1921
+ });
1105
1922
  const success = await workerManager.messageWorker(
1106
1923
  args.task,
1107
1924
  args.message,
1108
- args.interrupt
1925
+ args.interrupt,
1926
+ toolUseId
1109
1927
  );
1110
1928
  if (!success) {
1111
1929
  const worker = workerManager.getWorkerByTask(args.task);
@@ -1176,15 +1994,17 @@ var AgentOrchestrator = class {
1176
1994
  started = false;
1177
1995
  constructor(dependencies) {
1178
1996
  const resolveSessionIdBySdk = (sdkSessionId) => this.findSessionIdBySdkSession(sdkSessionId);
1179
- const workerClientFactory = dependencies?.createWorkerClient ?? ((mcpServers) => new AIClient({
1997
+ const workerClientFactory = dependencies?.createWorkerClient ?? ((mcpServers, cwd) => new AIClient({
1180
1998
  defaults: {
1181
1999
  allowedTools: [...BASE_ALLOWED_WORKER_TOOLS, "mcp__worker_status__send_status_update"],
1182
- mcpServers
2000
+ mcpServers,
2001
+ cwd: cwd || process.cwd()
1183
2002
  }
1184
2003
  }));
1185
2004
  this.workerManager = dependencies?.workerManager ?? new WorkerManager({
1186
2005
  createClient: workerClientFactory,
1187
- resolveSessionIdBySdk
2006
+ resolveSessionIdBySdk,
2007
+ repoPath: dependencies?.repoPath ?? process.cwd()
1188
2008
  });
1189
2009
  const workerMcpServer = createWorkerMcpServer(this.workerManager, {
1190
2010
  resolveSessionIdBySdk,
@@ -1273,6 +2093,7 @@ var AgentOrchestrator = class {
1273
2093
  this.started = true;
1274
2094
  }
1275
2095
  async stop() {
2096
+ await this.workerManager.shutdown();
1276
2097
  for (const connection of Array.from(this.clients.values())) {
1277
2098
  this.handleDisconnect(connection);
1278
2099
  try {
@@ -1775,76 +2596,6 @@ var AgentOrchestrator = class {
1775
2596
  }
1776
2597
  };
1777
2598
 
1778
- // src/auth/credentials-store.ts
1779
- import fs3 from "fs/promises";
1780
- import os2 from "os";
1781
- import path2 from "path";
1782
- var DEFAULT_DIR = path2.join(os2.homedir(), ".omnara");
1783
- var DEFAULT_CREDENTIALS_PATH = path2.join(DEFAULT_DIR, "creds.json");
1784
- var DEFAULT_FILE_MODE = 384;
1785
- var DEFAULT_DIR_MODE = 448;
1786
- function isStoredCredentials(value) {
1787
- return Boolean(value) && typeof value === "object";
1788
- }
1789
- var CredentialsStore = class {
1790
- constructor(filePath = DEFAULT_CREDENTIALS_PATH) {
1791
- this.filePath = filePath;
1792
- }
1793
- async load() {
1794
- try {
1795
- const raw = await fs3.readFile(this.filePath, "utf8");
1796
- const parsed = JSON.parse(raw);
1797
- if (!isStoredCredentials(parsed)) {
1798
- throw new Error("File does not contain valid credentials data");
1799
- }
1800
- return parsed;
1801
- } catch (error) {
1802
- if (isFileNotFound(error)) {
1803
- return null;
1804
- }
1805
- throw new Error(
1806
- `Failed to read credentials from ${this.filePath}: ${extractErrorMessage2(error)}`
1807
- );
1808
- }
1809
- }
1810
- async save(credentials) {
1811
- const payload = {
1812
- accessToken: credentials.accessToken,
1813
- accessTokenExpiresAt: credentials.accessTokenExpiresAt,
1814
- refreshToken: credentials.refreshToken,
1815
- refreshTokenExpiresAt: credentials.refreshTokenExpiresAt
1816
- };
1817
- await fs3.mkdir(path2.dirname(this.filePath), { recursive: true, mode: DEFAULT_DIR_MODE });
1818
- const tempPath = `${this.filePath}.tmp`;
1819
- const serialized = JSON.stringify(payload, null, 2);
1820
- await fs3.writeFile(tempPath, serialized, { mode: DEFAULT_FILE_MODE });
1821
- await fs3.chmod(tempPath, DEFAULT_FILE_MODE);
1822
- await fs3.rename(tempPath, this.filePath);
1823
- }
1824
- async clear() {
1825
- try {
1826
- await fs3.unlink(this.filePath);
1827
- } catch (error) {
1828
- if (!isFileNotFound(error)) {
1829
- throw new Error(
1830
- `Failed to remove credentials file ${this.filePath}: ${extractErrorMessage2(error)}`
1831
- );
1832
- }
1833
- }
1834
- }
1835
- };
1836
- function isFileNotFound(error) {
1837
- return Boolean(
1838
- error && typeof error === "object" && "code" in error && error.code === "ENOENT"
1839
- );
1840
- }
1841
- function extractErrorMessage2(error) {
1842
- if (error instanceof Error && error.message) {
1843
- return error.message;
1844
- }
1845
- return String(error);
1846
- }
1847
-
1848
2599
  // src/relay/relay-bridge.ts
1849
2600
  import { randomUUID as randomUUID4 } from "crypto";
1850
2601
  import EventEmitter2 from "events";
@@ -1865,8 +2616,8 @@ var ApiClient = class {
1865
2616
  constructor(options) {
1866
2617
  this.baseUrl = normalizeBaseUrl(options.baseUrl);
1867
2618
  }
1868
- async getJson(path5, options) {
1869
- const url = resolveUrl(this.baseUrl, path5);
2619
+ async getJson(path7, options) {
2620
+ const url = resolveUrl(this.baseUrl, path7);
1870
2621
  const headers = this.buildHeaders(options);
1871
2622
  const response = await fetch(url, {
1872
2623
  method: "GET",
@@ -1875,8 +2626,8 @@ var ApiClient = class {
1875
2626
  });
1876
2627
  return await this.handleResponse(response);
1877
2628
  }
1878
- async postJson(path5, body, options) {
1879
- const url = resolveUrl(this.baseUrl, path5);
2629
+ async postJson(path7, body, options) {
2630
+ const url = resolveUrl(this.baseUrl, path7);
1880
2631
  const headers = this.buildHeaders(options, { includeJson: true });
1881
2632
  const response = await fetch(url, {
1882
2633
  method: "POST",
@@ -1914,7 +2665,7 @@ var ApiClient = class {
1914
2665
  if (response.ok) {
1915
2666
  throw new ApiError(
1916
2667
  response.status,
1917
- `Failed to parse JSON response: ${extractErrorMessage3(error)}`
2668
+ `Failed to parse JSON response: ${extractErrorMessage4(error)}`
1918
2669
  );
1919
2670
  }
1920
2671
  }
@@ -1934,12 +2685,12 @@ function normalizeBaseUrl(raw) {
1934
2685
  }
1935
2686
  return raw.endsWith("/") ? raw : `${raw}/`;
1936
2687
  }
1937
- function resolveUrl(base, path5) {
2688
+ function resolveUrl(base, path7) {
1938
2689
  const baseUrl = new URL(base);
1939
- if (!path5.startsWith("/")) {
1940
- path5 = `/${path5}`;
2690
+ if (!path7.startsWith("/")) {
2691
+ path7 = `/${path7}`;
1941
2692
  }
1942
- const resolved = new URL(path5, baseUrl);
2693
+ const resolved = new URL(path7, baseUrl);
1943
2694
  return resolved.toString();
1944
2695
  }
1945
2696
  function extractDetail(payload) {
@@ -1962,7 +2713,7 @@ function extractDetail(payload) {
1962
2713
  }
1963
2714
  return null;
1964
2715
  }
1965
- function extractErrorMessage3(error) {
2716
+ function extractErrorMessage4(error) {
1966
2717
  if (error instanceof Error) {
1967
2718
  return error.message;
1968
2719
  }
@@ -2480,14 +3231,14 @@ function reportLoopbackError(error) {
2480
3231
  );
2481
3232
  return;
2482
3233
  }
2483
- console.log(`Automatic login failed: ${extractErrorMessage4(error)}`);
3234
+ console.log(`Automatic login failed: ${extractErrorMessage5(error)}`);
2484
3235
  }
2485
3236
  function printSessionInstructions(session) {
2486
3237
  console.log("\nOpen this link in your browser if it did not open automatically:");
2487
3238
  console.log(` ${session.verificationUriComplete}`);
2488
3239
  console.log("Approve the request, then return to the CLI.");
2489
3240
  }
2490
- function extractErrorMessage4(error) {
3241
+ function extractErrorMessage5(error) {
2491
3242
  if (error instanceof Error && error.message) {
2492
3243
  return error.message;
2493
3244
  }
@@ -2510,7 +3261,7 @@ async function waitForLoopback(listener, sessionId, onCode) {
2510
3261
  reportLoopbackError(error);
2511
3262
  }
2512
3263
  } catch (error) {
2513
- console.log(`Loopback listener error: ${extractErrorMessage4(error)}`);
3264
+ console.log(`Loopback listener error: ${extractErrorMessage5(error)}`);
2514
3265
  }
2515
3266
  }
2516
3267
 
@@ -4458,6 +5209,58 @@ var api_billing_subscription_response_schema_default = {
4458
5209
  ],
4459
5210
  default: null,
4460
5211
  title: "Provider"
5212
+ },
5213
+ plan_display_name: {
5214
+ anyOf: [
5215
+ {
5216
+ type: "string"
5217
+ },
5218
+ {
5219
+ type: "null"
5220
+ }
5221
+ ],
5222
+ default: null,
5223
+ description: "Human-friendly plan label (e.g. Pro, Starter).",
5224
+ title: "Plan Display Name"
5225
+ },
5226
+ plan_price_amount: {
5227
+ anyOf: [
5228
+ {
5229
+ type: "number"
5230
+ },
5231
+ {
5232
+ type: "null"
5233
+ }
5234
+ ],
5235
+ default: null,
5236
+ description: "Recurring charge amount for the plan (in major currency units).",
5237
+ title: "Plan Price Amount"
5238
+ },
5239
+ plan_price_currency: {
5240
+ anyOf: [
5241
+ {
5242
+ type: "string"
5243
+ },
5244
+ {
5245
+ type: "null"
5246
+ }
5247
+ ],
5248
+ default: null,
5249
+ description: "ISO currency for the recurring charge (e.g. usd).",
5250
+ title: "Plan Price Currency"
5251
+ },
5252
+ plan_price_interval: {
5253
+ anyOf: [
5254
+ {
5255
+ type: "string"
5256
+ },
5257
+ {
5258
+ type: "null"
5259
+ }
5260
+ ],
5261
+ default: null,
5262
+ description: "Billing interval for the recurring charge (e.g. month, year).",
5263
+ title: "Plan Price Interval"
4461
5264
  }
4462
5265
  },
4463
5266
  required: [
@@ -4647,9 +5450,79 @@ var api_billing_promo_validation_response_schema_default = {
4647
5450
  default: null,
4648
5451
  title: "Description"
4649
5452
  },
4650
- error: {
5453
+ error: {
5454
+ anyOf: [
5455
+ {
5456
+ type: "string"
5457
+ },
5458
+ {
5459
+ type: "null"
5460
+ }
5461
+ ],
5462
+ default: null,
5463
+ title: "Error"
5464
+ }
5465
+ },
5466
+ required: [
5467
+ "valid"
5468
+ ],
5469
+ type: "object"
5470
+ };
5471
+
5472
+ // ../shared-js/schemas/api_billing_customer_portal_request.schema.json
5473
+ var api_billing_customer_portal_request_schema_default = {
5474
+ $schema: "https://json-schema.org/draft/2020-12/schema",
5475
+ $id: "https://schemas.omnara.dev/CustomerPortalSessionRequest",
5476
+ title: "CustomerPortalSessionRequest",
5477
+ description: "Request to create a Stripe Billing customer portal session.",
5478
+ properties: {
5479
+ return_url: {
5480
+ anyOf: [
5481
+ {
5482
+ type: "string"
5483
+ },
5484
+ {
5485
+ type: "null"
5486
+ }
5487
+ ],
5488
+ default: null,
5489
+ description: "Where Stripe should send the customer after they leave the portal.",
5490
+ title: "Return Url"
5491
+ }
5492
+ },
5493
+ type: "object"
5494
+ };
5495
+
5496
+ // ../shared-js/schemas/api_billing_customer_portal_response.schema.json
5497
+ var api_billing_customer_portal_response_schema_default = {
5498
+ $schema: "https://json-schema.org/draft/2020-12/schema",
5499
+ $id: "https://schemas.omnara.dev/CustomerPortalSessionResponse",
5500
+ title: "CustomerPortalSessionResponse",
5501
+ description: "Response containing the Stripe Billing customer portal session URL.",
5502
+ properties: {
5503
+ url: {
5504
+ description: "Hosted Stripe customer portal URL.",
5505
+ title: "Url",
5506
+ type: "string"
5507
+ },
5508
+ created: {
5509
+ anyOf: [
5510
+ {
5511
+ format: "date-time",
5512
+ type: "string"
5513
+ },
5514
+ {
5515
+ type: "null"
5516
+ }
5517
+ ],
5518
+ default: null,
5519
+ description: "Timestamp when the session was created.",
5520
+ title: "Created"
5521
+ },
5522
+ expires_at: {
4651
5523
  anyOf: [
4652
5524
  {
5525
+ format: "date-time",
4653
5526
  type: "string"
4654
5527
  },
4655
5528
  {
@@ -4657,11 +5530,12 @@ var api_billing_promo_validation_response_schema_default = {
4657
5530
  }
4658
5531
  ],
4659
5532
  default: null,
4660
- title: "Error"
5533
+ description: "When the portal session expires (if provided by Stripe).",
5534
+ title: "Expires At"
4661
5535
  }
4662
5536
  },
4663
5537
  required: [
4664
- "valid"
5538
+ "url"
4665
5539
  ],
4666
5540
  type: "object"
4667
5541
  };
@@ -4801,6 +5675,8 @@ var validateCreateCheckoutSessionRequest = ajv.compile(api_billing_create_checko
4801
5675
  var validateCheckoutSessionResponse = ajv.compile(api_billing_checkout_response_schema_default);
4802
5676
  var validateValidatePromoCodeRequest = ajv.compile(api_billing_validate_promo_request_schema_default);
4803
5677
  var validatePromoCodeValidationResponse = ajv.compile(api_billing_promo_validation_response_schema_default);
5678
+ var validateCustomerPortalSessionRequest = ajv.compile(api_billing_customer_portal_request_schema_default);
5679
+ var validateCustomerPortalSessionResponse = ajv.compile(api_billing_customer_portal_response_schema_default);
4804
5680
  var validateUserNotificationSettingsRequest = ajv.compile(api_settings_notification_request_schema_default);
4805
5681
  var validateUserNotificationSettingsResponse = ajv.compile(api_settings_notification_response_schema_default);
4806
5682
 
@@ -4855,7 +5731,7 @@ var RelayBridge = class extends EventEmitter2 {
4855
5731
  break;
4856
5732
  }
4857
5733
  this.logger.error(
4858
- `Relay connection failed: ${extractErrorMessage5(error)}. Retrying in ${Math.round(backoffMs / 1e3)}s.`
5734
+ `Relay connection failed: ${extractErrorMessage6(error)}. Retrying in ${Math.round(backoffMs / 1e3)}s.`
4859
5735
  );
4860
5736
  await delay(backoffMs);
4861
5737
  backoffMs = Math.min(backoffMs * 2, 3e4);
@@ -4978,7 +5854,7 @@ var RelayBridge = class extends EventEmitter2 {
4978
5854
  target.send(serialized);
4979
5855
  return;
4980
5856
  } catch (error) {
4981
- this.logger.error(`Failed to send relay frame: ${extractErrorMessage5(error)}`);
5857
+ this.logger.error(`Failed to send relay frame: ${extractErrorMessage6(error)}`);
4982
5858
  if (target === this.ws) {
4983
5859
  this.closeCurrentSocket();
4984
5860
  }
@@ -5037,7 +5913,7 @@ var RelayBridge = class extends EventEmitter2 {
5037
5913
  const text = typeof data === "string" ? data : data.toString("utf8");
5038
5914
  parsed = JSON.parse(text);
5039
5915
  } catch (error) {
5040
- this.logger.error(`Received invalid JSON from relay: ${extractErrorMessage5(error)}`);
5916
+ this.logger.error(`Received invalid JSON from relay: ${extractErrorMessage6(error)}`);
5041
5917
  return;
5042
5918
  }
5043
5919
  if (this.tryHandleControlMessage(parsed)) {
@@ -5067,6 +5943,8 @@ var RelayBridge = class extends EventEmitter2 {
5067
5943
  }
5068
5944
  return frame;
5069
5945
  }
5946
+ case "user_text":
5947
+ return this.buildUserTextEnvelope(message);
5070
5948
  case "assistant_text":
5071
5949
  return this.buildTextEnvelope(message);
5072
5950
  case "tool_call":
@@ -5212,6 +6090,30 @@ var RelayBridge = class extends EventEmitter2 {
5212
6090
  isTextContent(content) {
5213
6091
  return typeof content.text === "string";
5214
6092
  }
6093
+ buildUserTextEnvelope(message) {
6094
+ const content = {
6095
+ type: "text",
6096
+ text: message.text
6097
+ };
6098
+ const payloadMetadata = message.workerId || message.workerTask ? {
6099
+ worker_id: message.workerId,
6100
+ worker_task: message.workerTask
6101
+ } : void 0;
6102
+ const senderSessionId = message.workerId ?? message.sessionId;
6103
+ const envelope = this.buildEnvelope({
6104
+ sessionId: message.sessionId,
6105
+ senderSessionId,
6106
+ content,
6107
+ payloadMetadata,
6108
+ messageId: message.messageId
6109
+ });
6110
+ envelope.metadata = {
6111
+ ...envelope.metadata,
6112
+ role: "user",
6113
+ transport: "relay"
6114
+ };
6115
+ return envelope;
6116
+ }
5215
6117
  buildTextEnvelope(message) {
5216
6118
  const content = {
5217
6119
  type: "text",
@@ -5379,7 +6281,7 @@ var RelayBridge = class extends EventEmitter2 {
5379
6281
  try {
5380
6282
  this.ws.send(payload);
5381
6283
  } catch (error) {
5382
- this.logger.error(`Failed to flush relay queue: ${extractErrorMessage5(error)}`);
6284
+ this.logger.error(`Failed to flush relay queue: ${extractErrorMessage6(error)}`);
5383
6285
  this.pendingOutbound.unshift(payload);
5384
6286
  this.closeCurrentSocket();
5385
6287
  break;
@@ -5410,23 +6312,23 @@ var RelayBridge = class extends EventEmitter2 {
5410
6312
  }
5411
6313
  }
5412
6314
  };
5413
- function buildRelayWebsocketUrl(baseUrl, path5) {
6315
+ function buildRelayWebsocketUrl(baseUrl, path7) {
5414
6316
  const url = new URL(baseUrl);
5415
6317
  url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
5416
6318
  url.search = "";
5417
6319
  url.hash = "";
5418
- if (path5.startsWith("/")) {
5419
- url.pathname = path5;
6320
+ if (path7.startsWith("/")) {
6321
+ url.pathname = path7;
5420
6322
  } else {
5421
6323
  const basePath = url.pathname.endsWith("/") ? url.pathname : `${url.pathname}/`;
5422
- url.pathname = `${basePath}${path5}`;
6324
+ url.pathname = `${basePath}${path7}`;
5423
6325
  }
5424
6326
  return url.toString();
5425
6327
  }
5426
6328
  function delay(ms) {
5427
6329
  return new Promise((resolve) => setTimeout(resolve, ms));
5428
6330
  }
5429
- function extractErrorMessage5(error) {
6331
+ function extractErrorMessage6(error) {
5430
6332
  if (error instanceof Error) {
5431
6333
  return error.message;
5432
6334
  }
@@ -5762,7 +6664,7 @@ Commands:
5762
6664
 
5763
6665
  // src/voice/voice-service.ts
5764
6666
  async function launchVoiceForSession(client, accessToken, options) {
5765
- const path5 = `/api/v1/voice/sessions/${options.sessionId}/launch`;
6667
+ const path7 = `/api/v1/voice/sessions/${options.sessionId}/launch`;
5766
6668
  const requestBody = {};
5767
6669
  if (options.force) {
5768
6670
  requestBody.force = true;
@@ -5771,7 +6673,7 @@ async function launchVoiceForSession(client, accessToken, options) {
5771
6673
  requestBody.room_name = options.roomName;
5772
6674
  }
5773
6675
  const response = await client.postJson(
5774
- path5,
6676
+ path7,
5775
6677
  requestBody,
5776
6678
  { accessToken }
5777
6679
  );
@@ -5796,50 +6698,47 @@ function checkClaudeCodeInstallation() {
5796
6698
  try {
5797
6699
  execSync("claude --version", {
5798
6700
  stdio: "ignore",
5799
- encoding: "utf-8"
6701
+ encoding: "utf-8",
6702
+ env: process.env
5800
6703
  });
5801
6704
  return true;
5802
6705
  } catch {
5803
6706
  return false;
5804
6707
  }
5805
6708
  }
6709
+ function checkAnthropicApiKey() {
6710
+ return !!process.env.ANTHROPIC_API_KEY && process.env.ANTHROPIC_API_KEY.trim().length > 0;
6711
+ }
5806
6712
  function displayClaudeCodeWarning() {
5807
- console.log(chalk.yellow("\nWarning: Claude Code is not installed or not found in PATH"));
5808
6713
  console.log(
5809
- chalk.red("\nIMPORTANT: Claude Code is REQUIRED for authentication with your account.")
6714
+ chalk.yellow(
6715
+ "\nWarning: Omnara needs access to the Anthropic API. Omnara didn't detect claude installed, nor did it find a valid API key. If you run into authentication errors, login via `claude login` or pass in an ANTHROPIC_API_KEY as an environment variable.\n"
6716
+ )
5810
6717
  );
5811
- console.log(chalk.white("\nClaude Code provides:"));
5812
- console.log(chalk.gray(" - AI-powered code assistance"));
5813
- console.log(chalk.gray(" - Intelligent code completion"));
5814
- console.log(chalk.gray(" - Natural language to code translation"));
5815
- console.log(chalk.gray(" - User authentication for API access"));
5816
- console.log(chalk.white("\nTo install Claude Code:"));
5817
- console.log(chalk.cyan(" 1. Visit: https://claude.ai/download"));
5818
- console.log(chalk.cyan(" 2. Download and install Claude Code for your platform"));
5819
- console.log(chalk.cyan(' 3. Make sure the "claude" command is available in your PATH'));
5820
- console.log(chalk.yellow("\nNote: Without Claude Code, you cannot authenticate and access"));
5821
- console.log(chalk.yellow("personalized features that require your user credentials.\n"));
5822
6718
  }
5823
6719
  function performClaudeCodeCheck(options = {}) {
5824
6720
  const { silent = false, exitOnMissing = false } = options;
5825
6721
  if (process.env.OMNARA_SKIP_CLAUDE_CHECK === "true") {
5826
6722
  return true;
5827
6723
  }
5828
- const isInstalled = checkClaudeCodeInstallation();
5829
- if (!isInstalled && !silent) {
6724
+ const isClaudeInstalled = checkClaudeCodeInstallation();
6725
+ const hasApiKey = checkAnthropicApiKey();
6726
+ const hasAuthentication = isClaudeInstalled || hasApiKey;
6727
+ if (!hasAuthentication && !silent) {
5830
6728
  displayClaudeCodeWarning();
5831
6729
  if (exitOnMissing) {
5832
- console.error(chalk.red("Exiting: Claude Code is required for this operation."));
6730
+ console.error(chalk.red("Exiting: Authentication is required for this operation."));
5833
6731
  process.exit(1);
5834
6732
  }
5835
6733
  }
5836
- return isInstalled;
6734
+ return hasAuthentication;
5837
6735
  }
5838
6736
  function getClaudeCodeVersion() {
5839
6737
  try {
5840
6738
  const version = execSync("claude --version", {
5841
6739
  encoding: "utf-8",
5842
- stdio: ["ignore", "pipe", "ignore"]
6740
+ stdio: ["ignore", "pipe", "ignore"],
6741
+ env: process.env
5843
6742
  }).trim();
5844
6743
  return version;
5845
6744
  } catch {
@@ -5847,233 +6746,6 @@ function getClaudeCodeVersion() {
5847
6746
  }
5848
6747
  }
5849
6748
 
5850
- // src/utils/logger.ts
5851
- import path3 from "path";
5852
- function isProduction() {
5853
- if (process.env.NODE_ENV === "production") {
5854
- return true;
5855
- }
5856
- try {
5857
- const currentFile = typeof __filename !== "undefined" ? __filename : "";
5858
- const isInDist = currentFile.includes(path3.sep + "dist" + path3.sep);
5859
- if (isInDist && process.env.NODE_ENV !== "development") {
5860
- return true;
5861
- }
5862
- } catch {
5863
- }
5864
- const logLevel = process.env.OMNARA_LOG_LEVEL?.toLowerCase();
5865
- if (logLevel === "none" || logLevel === "silent") {
5866
- return true;
5867
- }
5868
- return false;
5869
- }
5870
- function getLogLevel() {
5871
- const envLevel = process.env.OMNARA_LOG_LEVEL?.toLowerCase();
5872
- switch (envLevel) {
5873
- case "none":
5874
- case "silent":
5875
- return 0 /* NONE */;
5876
- case "error":
5877
- return 1 /* ERROR */;
5878
- case "warn":
5879
- case "warning":
5880
- return 2 /* WARN */;
5881
- case "info":
5882
- return 3 /* INFO */;
5883
- case "debug":
5884
- return 4 /* DEBUG */;
5885
- case "verbose":
5886
- case "trace":
5887
- return 5 /* VERBOSE */;
5888
- default:
5889
- return isProduction() ? 1 /* ERROR */ : 3 /* INFO */;
5890
- }
5891
- }
5892
- var ControlledLogger = class {
5893
- logLevel;
5894
- forceOutput;
5895
- _console;
5896
- // Console properties we need to implement
5897
- Console;
5898
- constructor(options) {
5899
- this.logLevel = options?.logLevel ?? getLogLevel();
5900
- this.forceOutput = options?.forceOutput ?? false;
5901
- this._console = console;
5902
- this.Console = console.Console;
5903
- }
5904
- /**
5905
- * Standard log output (INFO level)
5906
- */
5907
- log(...args) {
5908
- if (this.shouldLog(3 /* INFO */)) {
5909
- console.log(...args);
5910
- }
5911
- }
5912
- /**
5913
- * Error output (always shown unless NONE)
5914
- */
5915
- error(...args) {
5916
- if (this.shouldLog(1 /* ERROR */)) {
5917
- console.error(...args);
5918
- }
5919
- }
5920
- /**
5921
- * Warning output
5922
- */
5923
- warn(...args) {
5924
- if (this.shouldLog(2 /* WARN */)) {
5925
- console.warn(...args);
5926
- }
5927
- }
5928
- /**
5929
- * Info level output
5930
- */
5931
- info(...args) {
5932
- if (this.shouldLog(3 /* INFO */)) {
5933
- console.info(...args);
5934
- }
5935
- }
5936
- /**
5937
- * Debug output (only in development)
5938
- */
5939
- debug(...args) {
5940
- if (this.shouldLog(4 /* DEBUG */)) {
5941
- console.debug(...args);
5942
- }
5943
- }
5944
- /**
5945
- * Verbose/trace output
5946
- */
5947
- trace(...args) {
5948
- if (this.shouldLog(5 /* VERBOSE */)) {
5949
- console.log("[TRACE]", ...args);
5950
- }
5951
- }
5952
- /**
5953
- * Important messages that should always be shown to user
5954
- * (authentication status, connection info, etc.)
5955
- */
5956
- important(...args) {
5957
- if (this.logLevel !== 0 /* NONE */ || this.forceOutput) {
5958
- console.log(...args);
5959
- }
5960
- }
5961
- /**
5962
- * Success messages that should typically be shown
5963
- */
5964
- success(...args) {
5965
- if (this.shouldLog(2 /* WARN */) || this.forceOutput) {
5966
- console.log(...args);
5967
- }
5968
- }
5969
- /**
5970
- * Check if a message should be logged at the given level
5971
- */
5972
- shouldLog(level) {
5973
- return this.forceOutput || this.logLevel >= level;
5974
- }
5975
- /**
5976
- * Update the log level dynamically
5977
- */
5978
- setLogLevel(level) {
5979
- this.logLevel = level;
5980
- }
5981
- /**
5982
- * Get current log level
5983
- */
5984
- getLogLevel() {
5985
- return this.logLevel;
5986
- }
5987
- // Console methods we delegate or control based on log level
5988
- assert(condition, ...data) {
5989
- if (this.shouldLog(1 /* ERROR */)) {
5990
- this._console.assert(condition, ...data);
5991
- }
5992
- }
5993
- clear() {
5994
- this._console.clear();
5995
- }
5996
- count(label) {
5997
- if (this.shouldLog(4 /* DEBUG */)) {
5998
- this._console.count(label);
5999
- }
6000
- }
6001
- countReset(label) {
6002
- if (this.shouldLog(4 /* DEBUG */)) {
6003
- this._console.countReset(label);
6004
- }
6005
- }
6006
- dir(item, options) {
6007
- if (this.shouldLog(4 /* DEBUG */)) {
6008
- this._console.dir(item, options);
6009
- }
6010
- }
6011
- dirxml(...data) {
6012
- if (this.shouldLog(4 /* DEBUG */)) {
6013
- this._console.dirxml(...data);
6014
- }
6015
- }
6016
- group(...data) {
6017
- if (this.shouldLog(4 /* DEBUG */)) {
6018
- this._console.group(...data);
6019
- }
6020
- }
6021
- groupCollapsed(...data) {
6022
- if (this.shouldLog(4 /* DEBUG */)) {
6023
- this._console.groupCollapsed(...data);
6024
- }
6025
- }
6026
- groupEnd() {
6027
- if (this.shouldLog(4 /* DEBUG */)) {
6028
- this._console.groupEnd();
6029
- }
6030
- }
6031
- table(tabularData, properties) {
6032
- if (this.shouldLog(3 /* INFO */)) {
6033
- this._console.table(tabularData, properties);
6034
- }
6035
- }
6036
- time(label) {
6037
- if (this.shouldLog(4 /* DEBUG */)) {
6038
- this._console.time(label);
6039
- }
6040
- }
6041
- timeEnd(label) {
6042
- if (this.shouldLog(4 /* DEBUG */)) {
6043
- this._console.timeEnd(label);
6044
- }
6045
- }
6046
- timeLog(label, ...data) {
6047
- if (this.shouldLog(4 /* DEBUG */)) {
6048
- this._console.timeLog(label, ...data);
6049
- }
6050
- }
6051
- timeStamp(label) {
6052
- if (this.shouldLog(4 /* DEBUG */)) {
6053
- this._console.timeStamp(label);
6054
- }
6055
- }
6056
- profile(label) {
6057
- if (this.shouldLog(4 /* DEBUG */)) {
6058
- const consoleWithProfile = this._console;
6059
- consoleWithProfile.profile?.(label);
6060
- }
6061
- }
6062
- profileEnd(label) {
6063
- if (this.shouldLog(4 /* DEBUG */)) {
6064
- const consoleWithProfileEnd = this._console;
6065
- consoleWithProfileEnd.profileEnd?.(label);
6066
- }
6067
- }
6068
- };
6069
- var logger = new ControlledLogger();
6070
- function extractErrorMessage6(error) {
6071
- if (error instanceof Error) {
6072
- return error.message;
6073
- }
6074
- return String(error);
6075
- }
6076
-
6077
6749
  // src/commands/agent.ts
6078
6750
  function buildAgentCommand() {
6079
6751
  const agent = new Command("agent").description(
@@ -6118,7 +6790,7 @@ async function runAgentOrchestrator(options = {}) {
6118
6790
  try {
6119
6791
  await bridge.start();
6120
6792
  } catch (error) {
6121
- logger.error(`Relay orchestrator terminated: ${extractErrorMessage6(error)}`);
6793
+ logger.error(`Relay orchestrator terminated: ${extractErrorMessage2(error)}`);
6122
6794
  process.exitCode = process.exitCode || 1;
6123
6795
  } finally {
6124
6796
  await stop();
@@ -6203,16 +6875,16 @@ async function runVoice(options) {
6203
6875
  logger.debug(launch.relayToken);
6204
6876
  logger.important("\nKeep this information secure\u2014tokens grant access to your agent session.");
6205
6877
  }
6206
- function buildRelayWebsocketUrl2(baseUrl, path5) {
6878
+ function buildRelayWebsocketUrl2(baseUrl, path7) {
6207
6879
  const url = new URL(baseUrl);
6208
6880
  url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
6209
6881
  url.search = "";
6210
6882
  url.hash = "";
6211
- if (path5.startsWith("/")) {
6212
- url.pathname = path5;
6883
+ if (path7.startsWith("/")) {
6884
+ url.pathname = path7;
6213
6885
  } else {
6214
6886
  const basePath = url.pathname.endsWith("/") ? url.pathname : `${url.pathname}/`;
6215
- url.pathname = `${basePath}${path5}`;
6887
+ url.pathname = `${basePath}${path7}`;
6216
6888
  }
6217
6889
  return url.toString();
6218
6890
  }
@@ -6385,9 +7057,9 @@ function isMainModule() {
6385
7057
  if (!import.meta.url || !process.argv[1]) {
6386
7058
  return false;
6387
7059
  }
6388
- const executedPath = path4.resolve(process.argv[1]);
7060
+ const executedPath = path6.resolve(process.argv[1]);
6389
7061
  const currentPath = import.meta.url.startsWith("file://") ? decodeURIComponent(import.meta.url.slice(7)) : import.meta.url;
6390
- return path4.normalize(currentPath) === path4.normalize(executedPath);
7062
+ return path6.normalize(currentPath) === path6.normalize(executedPath);
6391
7063
  }
6392
7064
  if (isMainModule()) {
6393
7065
  run(process.argv).catch((error) => {