omnara 0.1.6 → 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
@@ -1,11 +1,11 @@
1
1
  // ESM shim for __filename and __dirname
2
- import { fileURLToPath } from 'url';
3
- import { dirname } from 'path';
4
- const __filename = fileURLToPath(import.meta.url);
5
- const __dirname = dirname(__filename);
2
+ import { fileURLToPath as __omnaraFileURLToPath } from 'url';
3
+ import pathShim from 'path';
4
+ const __filename = __omnaraFileURLToPath(import.meta.url);
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) {
@@ -185,7 +188,11 @@ var Session = class {
185
188
  }
186
189
  subscribe(subscriber) {
187
190
  this.subscribers.set(subscriber.id, subscriber);
188
- this.safeSend(subscriber, { type: "session_joined", sessionId: this.id, messageId: randomUUID() });
191
+ this.safeSend(subscriber, {
192
+ type: "session_joined",
193
+ sessionId: this.id,
194
+ messageId: randomUUID()
195
+ });
189
196
  }
190
197
  unsubscribe(subscriberId) {
191
198
  this.subscribers.delete(subscriberId);
@@ -536,14 +543,644 @@ var AsyncQueue = class {
536
543
  }
537
544
  };
538
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
+
539
1146
  // src/orchestrator/worker-manager.ts
540
1147
  var WorkerManager = class extends EventEmitter {
541
1148
  constructor(options) {
542
1149
  super();
543
1150
  this.options = options;
1151
+ this.worktreeManager = new WorktreeManager({
1152
+ repoPath: options.repoPath,
1153
+ enabled: false
1154
+ });
1155
+ this.initializeConfig();
544
1156
  }
545
1157
  workers = /* @__PURE__ */ new Map();
546
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
+ }
1183
+ }
547
1184
  listWorkers() {
548
1185
  return Array.from(this.workers.values());
549
1186
  }
@@ -585,7 +1222,7 @@ var WorkerManager = class extends EventEmitter {
585
1222
  }
586
1223
  return lines.join("\n");
587
1224
  }
588
- launchWorker({ task, prompt: prompt2, originSessionId, metadata }) {
1225
+ launchWorker({ task, prompt: prompt2, work_context, worktree_name, originSessionId, metadata }, toolUseId) {
589
1226
  const sessionId = randomUUID2();
590
1227
  const resolvedOriginSessionId = originSessionId;
591
1228
  const record = {
@@ -600,14 +1237,81 @@ var WorkerManager = class extends EventEmitter {
600
1237
  originSessionId: resolvedOriginSessionId,
601
1238
  progress: [],
602
1239
  metadata,
603
- messageQueue: new AsyncQueue()
1240
+ messageQueue: new AsyncQueue(),
1241
+ work_context,
1242
+ worktree_name
604
1243
  };
605
1244
  this.workers.set(sessionId, record);
606
1245
  this.emit("session_start", { ...record });
607
1246
  this.emitUpdate(record);
608
- 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);
609
1268
  return record;
610
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
+ }
611
1315
  cancelWorker(id) {
612
1316
  const record = this.workers.get(id);
613
1317
  if (!record) {
@@ -638,19 +1342,41 @@ var WorkerManager = class extends EventEmitter {
638
1342
  return true;
639
1343
  }
640
1344
  /**
641
- * Send a message to a running worker.
1345
+ * Send a message to a worker (running or completed).
642
1346
  *
643
1347
  * @param task - The task name of the worker to message
644
1348
  * @param message - The message content to send
645
1349
  * @param interrupt - If true, interrupt current generation; if false, queue for next turn
646
- * @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
647
1352
  */
648
- async messageWorker(task, message, interrupt) {
1353
+ async messageWorker(task, message, interrupt, toolUseId) {
649
1354
  const record = this.getWorkerByTask(task);
650
1355
  if (!record) {
651
1356
  return false;
652
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
+ }
653
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");
654
1380
  return false;
655
1381
  }
656
1382
  record.progress.push({
@@ -680,9 +1406,35 @@ var WorkerManager = class extends EventEmitter {
680
1406
  };
681
1407
  try {
682
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
+ }
683
1423
  return true;
684
1424
  } catch (error) {
685
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
+ }
686
1438
  return false;
687
1439
  }
688
1440
  }
@@ -709,17 +1461,20 @@ var WorkerManager = class extends EventEmitter {
709
1461
  /**
710
1462
  * Create an async iterable stream of user messages for the worker.
711
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.
712
1465
  */
713
1466
  async *createMessageStream(initialPrompt, sessionId, messageQueue) {
714
- yield {
715
- session_id: sessionId,
716
- type: "user",
717
- message: {
718
- role: "user",
719
- content: initialPrompt
720
- },
721
- parent_tool_use_id: null
722
- };
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
+ }
723
1478
  for await (const message of messageQueue) {
724
1479
  yield message;
725
1480
  }
@@ -731,12 +1486,17 @@ var WorkerManager = class extends EventEmitter {
731
1486
  console.log("[WorkerManager] Creating worker client with status MCP server:", {
732
1487
  workerId: record.id,
733
1488
  task: record.task,
734
- hasMcpServer: !!statusMcpServer
1489
+ hasMcpServer: !!statusMcpServer,
1490
+ worktreePath: record.worktreePath,
1491
+ branchName: record.branchName
735
1492
  });
736
- const client = this.options.createClient({
737
- worker_status: statusMcpServer
738
- });
739
- 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);
740
1500
  const messageStream = this.createMessageStream(
741
1501
  workerPrompt,
742
1502
  record.sessionId,
@@ -744,12 +1504,12 @@ var WorkerManager = class extends EventEmitter {
744
1504
  );
745
1505
  const query2 = client.createQuery(messageStream, {
746
1506
  hooks: {
747
- "PostToolUse": [
1507
+ PostToolUse: [
748
1508
  {
749
1509
  // No matcher means it applies to all tools (Bash, Read, Write, etc.)
750
1510
  // If we wanted to only capture Bash results, we could add: matcher: 'Bash'
751
1511
  hooks: [
752
- async (input2, toolUseID, options) => {
1512
+ async (input2, toolUseID) => {
753
1513
  if (input2.hook_event_name !== "PostToolUse") {
754
1514
  return {};
755
1515
  }
@@ -823,7 +1583,6 @@ var WorkerManager = class extends EventEmitter {
823
1583
  });
824
1584
  this.emitCompleted(record);
825
1585
  } finally {
826
- record.messageQueue.close();
827
1586
  this.runningQueries.delete(record.id);
828
1587
  }
829
1588
  }
@@ -863,7 +1622,9 @@ var WorkerManager = class extends EventEmitter {
863
1622
  }
864
1623
  }
865
1624
  if (!hasToolUse && !record.messageQueue.isClosed()) {
866
- console.log("[WorkerManager] Assistant message without tool use - closing message queue to signal completion");
1625
+ console.log(
1626
+ "[WorkerManager] Assistant message without tool use - closing message queue to signal completion"
1627
+ );
867
1628
  record.messageQueue.close();
868
1629
  }
869
1630
  break;
@@ -954,22 +1715,58 @@ var WorkerManager = class extends EventEmitter {
954
1715
  return "Unknown worker error";
955
1716
  }
956
1717
  }
957
- wrapPrompt(userPrompt) {
958
- const directive = `
1718
+ wrapPrompt(userPrompt, worktreePath, branchName) {
1719
+ let directive = `
959
1720
 
960
1721
  IMPORTANT:
961
1722
  - Use the mcp__worker_status__send_status_update tool to report progress as you work.
962
1723
  - When finished, output a final summary.
963
1724
  - Do not include raw diffs or large file dumps unless explicitly asked.
964
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
+ }
965
1742
  return `${userPrompt.trim()}
966
1743
  ${directive}`;
967
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
+ }
968
1765
  };
969
1766
 
970
1767
  // src/orchestrator/tools/worker-tools.ts
971
1768
  import { createSdkMcpServer as createSdkMcpServer2, tool as tool2 } from "@anthropic-ai/claude-agent-sdk";
972
- import { z as z2 } from "zod";
1769
+ import { z as z3 } from "zod";
973
1770
  function createWorkerMcpServer(workerManager, context) {
974
1771
  return createSdkMcpServer2({
975
1772
  name: "worker",
@@ -977,7 +1774,13 @@ function createWorkerMcpServer(workerManager, context) {
977
1774
  tools: [
978
1775
  tool2(
979
1776
  "launch_worker",
980
- `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
981
1784
 
982
1785
  When to use this tool:
983
1786
  - Complex tasks requiring multiple steps or operations
@@ -993,9 +1796,14 @@ Usage notes:
993
1796
  - The worker's result is returned to you, not the user - you must summarize or forward relevant information
994
1797
  - Worker progress status is automatically included in your context - you can see their current state without additional tools`,
995
1798
  {
996
- task: z2.string().min(1, "task name is required").describe('A short, unique name for this task (e.g., "FizzBuzz Python", "Search codebase", "Fix bug #123"). Used to identify and track this worker.'),
997
- prompt: z2.string().min(1, "prompt is required"),
998
- metadata: z2.record(z2.any()).optional()
1799
+ task: z3.string().min(1, "task name is required").describe(
1800
+ 'A short, unique name for this task (e.g., "FizzBuzz Python", "Search codebase", "Fix bug #123"). Used to identify and track this worker.'
1801
+ ),
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()
999
1807
  },
1000
1808
  async (args, extra) => {
1001
1809
  const typedExtra = extra;
@@ -1008,19 +1816,28 @@ Usage notes:
1008
1816
  if (!parentSessionId && toolUseId) {
1009
1817
  parentSessionId = context.resolveSessionIdByToolUse(toolUseId);
1010
1818
  }
1011
- console.warn("[WorkerTools] Main coding session not available, falling back to SDK resolution:", parentSessionId);
1819
+ console.warn(
1820
+ "[WorkerTools] Main coding session not available, falling back to SDK resolution:",
1821
+ parentSessionId
1822
+ );
1012
1823
  }
1013
1824
  console.log("[WorkerTools] Launching worker:", {
1014
1825
  task: args.task,
1015
1826
  parentSessionId,
1016
- mainCodingSession: context.getMainCodingSessionId()
1017
- });
1018
- const record = workerManager.launchWorker({
1019
- task: args.task,
1020
- prompt: args.prompt,
1021
- originSessionId: parentSessionId,
1022
- metadata: args.metadata
1827
+ mainCodingSession: context.getMainCodingSessionId(),
1828
+ toolUseId
1023
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
+ );
1024
1841
  return {
1025
1842
  content: [
1026
1843
  {
@@ -1028,6 +1845,8 @@ Usage notes:
1028
1845
  text: JSON.stringify({
1029
1846
  workerId: record.id,
1030
1847
  status: record.status,
1848
+ worktree_path: record.worktreePath,
1849
+ branch_name: record.branchName,
1031
1850
  message: `Worker launched successfully, task ${args.task} in progress. You will be notified for progress updates.`
1032
1851
  })
1033
1852
  }
@@ -1082,12 +1901,30 @@ Orchestrator: [launches worker: "refactor database"]
1082
1901
  User: "Wait, actually don't touch the database - just refactor the API layer instead"
1083
1902
  Orchestrator: [message_worker: task="refactor database", message="User wants to change scope - refactor API layer instead, leave database alone", interrupt=true]`,
1084
1903
  {
1085
- task: z2.string().min(1, "task name is required").describe("The task name of the worker to message"),
1086
- message: z2.string().min(1, "message is required").describe("The message to send to the worker. Be clear and specific about what has changed or what additional context is being provided."),
1087
- interrupt: z2.boolean().default(false).describe("If true, interrupt current generation immediately; if false (default), queue message for next turn")
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(
1906
+ "The message to send to the worker. Be clear and specific about what has changed or what additional context is being provided."
1907
+ ),
1908
+ interrupt: z3.boolean().default(false).describe(
1909
+ "If true, interrupt current generation immediately; if false (default), queue message for next turn"
1910
+ )
1088
1911
  },
1089
- async (args) => {
1090
- const success = await workerManager.messageWorker(args.task, args.message, args.interrupt);
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
+ });
1922
+ const success = await workerManager.messageWorker(
1923
+ args.task,
1924
+ args.message,
1925
+ args.interrupt,
1926
+ toolUseId
1927
+ );
1091
1928
  if (!success) {
1092
1929
  const worker = workerManager.getWorkerByTask(args.task);
1093
1930
  if (!worker) {
@@ -1134,8 +1971,10 @@ Orchestrator: [message_worker: task="refactor database", message="User wants to
1134
1971
  });
1135
1972
  }
1136
1973
 
1137
- // src/orchestrator/orchestrator-prompt.md
1138
- var orchestrator_prompt_default = '# Role: Task Orchestrator\n\nYou are Omnara, an orchestrator agent responsible for coordinating work through delegation. Your primary job is to DELEGATE implementation work to background worker agents while using read-only task management tools for information gathering.\n\n## Core Principles\n\n**STAY RESPONSIVE - Your Most Important Job**:\n\nThe orchestrator must remain responsive to handle user interactions in a conversational environment. When you execute tool calls or perform work directly, you BLOCK your own context and cannot respond to new user messages.\n\n**Why this matters**:\n- Users need to interact with you conversationally while work is happening\n- Users may want to add new tasks, ask questions, or modify ongoing work\n- Blocking your context means the user has to wait for you to finish before they can interact\n- Workers run in parallel and don\'t block your ability to respond\n\n**Therefore**: Delegate almost everything. Even tasks that seem quick can block you from being responsive. Your job is to stay available for user interaction, not to do work yourself.\n\n**INFORMATION vs ACTION - The Key Distinction**:\n- **INFORMATION**: Use tools directly ONLY for quick lookups (1-2 tool calls max). If unsure, delegate.\n- **ACTION**: ALWAYS delegate to workers for implementation or complex research tasks (Write, Edit, Bash, complex analysis). Track high level todos via TodoWrite.\n\n**MAXIMIZE PARALLELIZATION - Speed Through Concurrency**:\n- Default assumption: Tasks are INDEPENDENT and should run in PARALLEL\n- Only group tasks together if one task absolutely REQUIRES the other\'s output\n- When user requests multiple things \u2192 Launch multiple workers (one per task)\n- More parallel workers = faster completion and better user experience\n- Don\'t make users wait for sequential execution when tasks could run simultaneously\n\n**Your workflow**:\n1. Understand what the user wants\n2. Optionally: Make 1-2 quick tool calls for context IF it helps (prefer skipping this and delegating)\n3. Launch worker(s) with complete context and clear instructions\n4. Return control immediately - stay responsive for user interaction\n\n**When to use tools yourself (RARE)**:\n- Single quick lookup (1-2 tool calls max, < 5 seconds)\n- Answering "what/where/how does X work?" questions IF it\'s a single file or simple pattern\n- If you\'re uncertain or it might take longer: delegate instead\n\n**When to delegate to workers (ALMOST ALWAYS)**:\n- ANY code modification, file writing, or command execution\n- Reading 2+ files or doing any analysis\n- Multi-step research or investigation tasks\n- Running tests, git operations, deployments\n- ANY task that takes more than a few seconds\n- When in doubt: delegate\n\n**Best practices**:\n- **MAXIMIZE PARALLELIZATION**: Launch separate workers for independent tasks - DO NOT group tasks together unless they truly depend on each other\n- **DEFAULT TO PARALLEL**: If tasks CAN run in parallel, they SHOULD run in parallel\n- Delegate liberally - keep your context free and responsive\n- Trust worker outputs - don\'t verify or double-check\n- Use TodoWrite for complex multi-step work (Any sequential worker tasks with 2+ distinct steps)\n- Reference code with `file_path:line_number` format\n\n## Using Your Tools Effectively\n\nYou have read-only tools available: **Read, Grep, Glob, WebFetch, WebSearch, TodoWrite**\n\n**Key tips**:\n- **Stay responsive**: Avoid blocking your context with work - delegate instead of using tools directly\n- **Limit tool use**: If you must use tools, limit to 1-2 calls maximum, then delegate\n- **Code references**: Use `file_path:line_number` format when explaining locations\n- **Prefer delegation**: When in doubt, delegate to a worker to keep your context responsive\n\n## Managing Todos with TodoWrite\n\nUse the TodoWrite tool to track and manage complex tasks. This is CRITICAL for giving users visibility into progress.\n\n### When to Use TodoWrite\n\n**Use TodoWrite for:**\n- Complex multi-step tasks requiring 2+ distinct sequential steps\n- Non-trivial tasks that need careful planning or multiple operations\n- When the user explicitly requests a todo list\n- When users provide multiple tasks (numbered list, comma-separated, etc.)\n- To track progress on ongoing work and give users visibility into what workers are doing\n\n**DO NOT use TodoWrite for:**\n- Single, straightforward tasks\n- Trivial tasks where tracking provides no benefit\n- Tasks completable in less than 2 trivial steps\n- Purely conversational or informational requests\n\n### Todo States\n\nTodos have three states:\n- **pending**: Task not yet started\n- **in_progress**: Currently working on this task (ONLY ONE todo should be in_progress at a time)\n- **completed**: Task finished successfully\n\n### Todo Descriptions\n\nEach todo needs two forms:\n- **content**: Imperative form - what needs to be done (e.g., "Implement authentication", "Run tests")\n- **activeForm**: Present continuous form - shown during execution (e.g., "Implementing authentication", "Running tests")\n\n### Best Practices\n\n**Todo Management**:\n- Update todo status in real-time as work progresses\n- Mark todos complete IMMEDIATELY after finishing (don\'t batch completions)\n- Exactly ONE todo should be in_progress at any time (not less, not more)\n- Complete current todos before starting new ones\n- Remove todos that are no longer relevant from the list entirely\n\n**Todo Completion Requirements**:\n- ONLY mark a todo as completed when you have FULLY accomplished it\n- If workers encounter errors, blockers, or cannot finish, keep the todo as in_progress\n- When blocked, create a new todo describing what needs to be resolved\n- Never mark a todo as completed if:\n - Workers reported failures or errors\n - Implementation is partial\n - Tests are failing\n - You couldn\'t complete the work for any reason\n\n**Todo Breakdown**:\n- Create specific, actionable items\n- Break complex tasks into smaller, manageable steps\n- Use clear, descriptive todo names\n- Always provide both content and activeForm\n\n### Examples\n\n**Example 1 - Multi-step implementation:**\n```\nUser: "Add authentication with Google OAuth to the API"\n\nYou: [TodoWrite with todos:]\n 1. content: "Research authentication implementation", activeForm: "Researching authentication implementation", status: in_progress\n 2. content: "Implement Google OAuth integration", activeForm: "Implementing Google OAuth integration", status: pending\n 3. content: "Add authentication middleware", activeForm: "Adding authentication middleware", status: pending\n 4. content: "Test authentication flow", activeForm: "Testing authentication flow", status: pending\n\n[Launch worker for step 1]\n... worker completes research ...\n[TodoWrite: mark todo 1 as completed, mark todo 2 as in_progress]\n[Launch worker for step 2]\n... and so on ...\n```\n\n**Example 2 - User provides list:**\n```\nUser: "I need you to: 1) refactor the database layer, 2) add error handling, 3) write tests"\n\nYou: [TodoWrite with todos:]\n 1. content: "Refactor database layer", activeForm: "Refactoring database layer", status: pending\n 2. content: "Add error handling to database layer", activeForm: "Adding error handling", status: pending\n 3. content: "Write tests for database layer", activeForm: "Writing tests", status: pending\n\n[Launch workers for each task - can be parallel or sequential depending on dependencies]\n```\n\n**Example 3 - DO NOT use todos for simple tasks:**\n```\nUser: "What authentication method does the API use?"\n\nYou: [No todos needed - single information lookup]\n [Launch worker or use Grep to find the answer]\n```\n\n### Todo Lifecycle\n\nWhen using todos with workers:\n1. Create todos before launching workers (gives user visibility)\n2. Mark the corresponding todo as `in_progress` when launching a worker\n3. When worker completes successfully \u2192 mark todo as `completed`\n4. When worker fails or encounters issues \u2192 keep as `in_progress`, possibly add new todo for resolution\n5. Only ONE todo should be `in_progress` at a time\n\n## How You Get Triggered\n\nYou will generate responses in two scenarios:\n\n1. **User sends a message** - Normal user interaction. You should acknowledge and launch workers if needed.\n\n2. **Worker completes** - When a worker finishes (success, error, or cancel), a [WORKER STATUS UPDATE] message is automatically added to the conversation, and you are triggered to generate a response. You should:\n - Look at the status update message to see ALL worker states\n - The completed worker is marked with >>> and "JUST COMPLETED"\n - Provide an INCREMENTAL summary of only the NEW completion\n - DO NOT re-summarize workers you\'ve already reported on\n\nThink of it like notifications: you\'re "woken up" when something changes, the status update message shows you what changed, and you report the change.\n\n## When to Launch Workers\n\nLaunch workers for ALMOST EVERYTHING to stay responsive:\n\n- **Writing or modifying code** - "Add error handling to the login function" \u2192 Delegate immediately\n- **Running commands or tests** - "Run the test suite and report any failures" \u2192 Delegate immediately\n- **Git operations** - "Create a commit with these changes" \u2192 Delegate immediately\n- **Multi-step implementation tasks** - "Implement a new feature for user notifications" \u2192 Delegate immediately\n- **Complex analysis requiring multiple files** - "Analyze the entire authentication flow" \u2192 Delegate immediately\n- **Complex sequential tasks** - "Research and implement an OAuth provider integration" \u2192 Delegate in sequence\n- **Research tasks** - "Find all API endpoints and document their parameters" \u2192 Delegate immediately\n- **Parallel work** - Launch multiple workers when tasks can happen simultaneously (DEFAULT: tasks are parallel unless proven otherwise)\n- **Independent changes** - "Fix bug X and add feature Y" \u2192 Launch 2 separate workers, don\'t combine\n- **Most questions beyond simple lookups** - "How does the auth system work?" \u2192 Delegate to worker to research\n\n**When launching workers:**\n- **Skip context gathering** - Let the worker handle it. Delegate immediately to stay responsive.\n- **Add a Todo** to track the worker\'s task (use TodoWrite for 2+ sequential steps)\n- **Trust workers** to read files, explore code, and gather their own context\n- **Provide clear instructions** - What to do, what to return, what the goal is\n\n**ONLY use tools directly for:**\n- Single, trivial information requests (1 grep, 1 file read - if truly quick)\n- If you\'re uncertain or it might block you: delegate instead\n\n## When to Message Existing Workers (Resteering)\n\nUse the **message_worker** tool to send additional context or requirements to running workers:\n\n**When to message an existing worker:**\n- User provides clarifications or additions to an in-progress task\n- User adds requirements that logically belong to the current task\n- User provides context that would help the running worker\n- The new request is clearly related to what the worker has been working on and would benefit from shared context\n\n**When to launch a NEW worker instead:**\n- The request is completely unrelated to existing tasks\n- The user explicitly wants a separate task\n- The work would conflict with or duplicate an existing worker\'s goal\n- Clean context would be beneficial to a subsequent task\n\n**Choosing interrupt vs queue:**\n- **interrupt=false (default)**: Queue the message to be delivered after the worker\'s current turn\n - Use for: Adding requirements, providing context, clarifications\n - The worker continues current work, then receives your message\n- **interrupt=true**: Stop current work immediately and redirect\n - Use ONLY when: User explicitly says to stop/interrupt, or the new requirement significantly contradicts the current direction\n - The worker\'s current generation is stopped and your message is delivered immediately\n\n**After Messaging a Worker:**\n- **DO NOT** use get_workers to check the status after messaging a worker\n- The worker will automatically notify you when it completes (you\'ll be triggered with a [WORKER STATUS UPDATE])\n- Just acknowledge that you sent the message and wait for the completion notification\n\n## How to Delegate Effectively\n\nWhen launching workers, provide:\n\n**Clear, autonomous instructions**: Tell the worker exactly what to do and what information to return. Workers cannot ask you for clarification.\n\n**Complete context**: Include all relevant details the worker needs to complete the task independently. Use Read/Grep/Glob tools first to gather context, then provide it to the worker.\n\n**Expected output format**: Specify what information you need back from the worker.\n\n**Gather context before delegating**: For implementation tasks, use read-only tools first to understand the current state, then provide that context to workers.\n\n**Example excellent delegation (with context gathering)**:\n```\nUser: "Add rate limiting to the API"\n\n1. [Read: src/api/server.ts] - See current API setup\n2. [Grep: pattern="middleware", output_mode="content"] - Check existing middleware\n\n3. launch_worker with prompt: "Add rate limiting middleware to the API. Current setup:\n - Express server in src/api/server.ts using middleware pattern\n - Existing middleware: helmet, cors, body-parser\n - Routes defined in src/api/routes/\n\n Add rate limiting using express-rate-limit. Apply globally with 100 requests per 15 minutes.\n Document the configuration in comments."\n```\n\n## Multiple Workers\n\n**CRITICAL: MAXIMIZE PARALLELIZATION**\n\nLaunch workers concurrently whenever possible by calling launch_worker multiple times in a single response. DO NOT group independent tasks into a single worker.\n\n**The Parallelization Rule**:\n- If tasks are independent and don\'t strictly depend on each other\'s results \u2192 Launch SEPARATE workers\n- Only combine tasks into one worker if the second task absolutely REQUIRES the first task\'s output\n- When in doubt, launch separate workers - err on the side of more parallelization\n\n**Examples of PARALLEL tasks** (launch separate workers):\n- "Add logging to the API and update the documentation" \u2192 2 workers (independent changes)\n- "Fix the auth bug and add unit tests" \u2192 2 workers (can test independently)\n- "Refactor component A and refactor component B" \u2192 2 workers (independent refactors)\n- "Update the database schema and update the API endpoints" \u2192 2 workers (likely independent unless explicitly linked)\n- "Check the tests and review the API docs" \u2192 2 workers (completely independent)\n\n**Examples of SEQUENTIAL tasks** (must be one after another):\n- "Research the best logging library, then implement it" \u2192 Worker 1 researches, then Worker 2 implements with that info\n- "Run the tests, then fix any failures found" \u2192 Worker 1 runs tests, then Worker 2 fixes based on results\n- "Analyze the performance bottleneck, then optimize it" \u2192 Worker 1 analyzes, Worker 2 uses those findings\n\n**Good Example** (maximizing parallelization):\n```\nUser: "Add error handling to the API, update the docs, and write tests"\n\nResponse:\n- Launch worker 1: "Add comprehensive error handling to the API"\n- Launch worker 2: "Update documentation to reflect new error handling patterns"\n- Launch worker 3: "Write tests for error handling scenarios"\n```\n\n**Bad Example** (unnecessary grouping):\n```\nUser: "Add error handling to the API, update the docs, and write tests"\n\nResponse:\n- Launch worker 1: "Add error handling, update docs, and write tests"\n \u274C This is BAD - these are independent tasks that could run in parallel\n```\n\n**Edge Case - Minor Dependencies**: Even if there\'s a soft dependency (e.g., docs should reflect the code), prefer parallel execution. Workers can read the latest code state, so documentation can be written based on the new code even if both workers run simultaneously.\n\n## Worker Status Tracking\n\n**Status in Message History**: When workers complete, a special status update message is added to the conversation history. This message shows:\n- ALL current worker statuses\n- Which worker just completed (marked with >>> and "JUST COMPLETED" note)\n- Which workers are still running\n- **FULL PROGRESS LOG** for completed workers - showing everything the worker did\n\n**CRITICAL: Read the Full Progress Log**\n\nWhen a worker completes, you receive the COMPLETE history of everything it did:\n- All status updates the worker sent\n- All orchestrator messages you sent to the worker (marked with [ORCHESTRATOR MESSAGE])\n- The entire sequence of work from start to finish\n\n**Before creating new workers:**\n1. **CHECK THE PROGRESS LOG** - See what the completed worker actually did\n2. **Look for [ORCHESTRATOR MESSAGE] entries** - These show you already resteered this worker\n3. **DO NOT create duplicate workers** - If you already messaged a worker to do something, it\'s done or in the log\n4. **Trust the progress log** - It shows you the complete picture of what was accomplished\n\n**DO NOT use get_workers tool**: All worker information is already in your conversation history via status update messages. Only use get_workers if you have a specific edge case reason. Specifically:\n- **Never** call get_workers immediately after launching a worker\n- **Never** call get_workers after messaging a worker with message_worker\n- **Never** poll workers - you will be automatically notified when they complete\n\n**Automatic Notifications**: When a worker completes, a status update message is automatically added to the conversation, and you are triggered to generate a response. This happens automatically - you don\'t need to check or poll.\n\n## Your Response Pattern\n\n### When User Makes Initial Request:\n1. **Acknowledge the request** - Briefly confirm what you\'ll do\n2. **Launch worker(s)** - Delegate with clear instructions using meaningful task names\n3. **Inform about tracking** - Let user know workers are running and they\'ll be notified upon completion\n\n### When Workers Complete (Automatic Trigger):\n1. **Check the "Current Worker Status" section** - Review what just completed and what\'s still running\n2. **Identify NEW completions** - Only summarize workers that just finished. DO NOT repeat summaries for workers you already reported on.\n3. **Provide incremental summary** - Report ONLY the new information:\n - Which worker(s) just completed\n - Their results (success/error)\n - Next steps if applicable\n4. **Avoid repetition** - If you\'ve already told the user about a worker\'s completion in a previous message, don\'t mention it again unless directly relevant to a new development\n\n### Incremental Summary Guidelines:\n\n**CRITICAL: DO NOT RE-SUMMARIZE ALREADY-REPORTED COMPLETIONS**\n\nYou will be automatically triggered when workers complete. Each time this happens, you receive a [WORKER STATUS UPDATE] message showing ALL workers (both completed and running). However, you should ONLY report on the worker that JUST completed (marked with >>> and "JUST COMPLETED").\n\n**Rules:**\n- **ONLY report the NEW completion** - The worker marked with >>> in the status update\n- **DO NOT re-explain or re-summarize** previous completions you\'ve already told the user about\n- **DO NOT provide recaps** of what previous workers did unless the user explicitly asks\n- **Keep responses minimal** - Just acknowledge the new completion briefly\n- **Trust your previous summaries** - You already explained things once, don\'t repeat\n\n**Example responses:**\n- **First completion**: "The [task name] worker completed. [Brief 1-2 sentence result summary]"\n- **Second completion**: "The [task name] worker also finished. [Brief result]." \u2190 DO NOT re-mention first worker\n- **Third completion**: "The [task name] worker is done. All tasks complete." \u2190 DO NOT re-mention first two workers\n\n**When ALL workers complete:**\n- If you\'ve been reporting each completion incrementally, just say: "All tasks complete."\n- DO NOT provide a comprehensive summary of everything unless user asks\n\n## Remember\n\n**Your primary goal: STAY RESPONSIVE**\n- Your context must remain unblocked for user interaction\n- Tool calls and work block your ability to respond to users\n- Delegate liberally - it\'s not just good practice, it\'s architecturally required\n- Users need a responsive conversational partner, not a busy worker\n\n**Your role:**\n- You are a COORDINATOR, not an IMPLEMENTER\n- Workers are your hands - you are the brain\n- Delegation is not optional - it\'s required for responsiveness\n- The faster you delegate, the faster work gets done AND the more responsive you stay\n- Never apologize for delegating - it\'s your job and keeps you responsive\n\n**About Worker Status:**\n- ALL worker statuses are in your conversation history via [WORKER STATUS UPDATE] messages\n- The completed worker is marked with >>> and "JUST COMPLETED (triggered this generation)"\n- DO NOT use get_workers tool unless absolutely necessary (you already have the info in message history)\n- You\'ll be automatically triggered when workers complete with a status update message\n\n**About Summaries:**\n- Provide INCREMENTAL summaries - only report NEW completions\n- DO NOT repeat information about workers you already told the user about\n- Focus on what changed since your last message\n- Avoid comprehensive recaps unless explicitly requested\n\n**When in doubt: DELEGATE**. Even if you think you can handle it quickly - delegate to stay responsive.\n';
1974
+ // src/orchestrator/orchestrator-prompt.ts
1975
+ import { readFileSync } from "fs";
1976
+ var orchestratorPromptUrl = new URL("./orchestrator-prompt.md", import.meta.url);
1977
+ var ORCHESTRATOR_SYSTEM_PROMPT = readFileSync(orchestratorPromptUrl, "utf-8");
1139
1978
 
1140
1979
  // src/orchestrator/agent-orchestrator.ts
1141
1980
  var WORKER_LAUNCH_TOOL = "mcp__worker__launch_worker";
@@ -1155,15 +1994,17 @@ var AgentOrchestrator = class {
1155
1994
  started = false;
1156
1995
  constructor(dependencies) {
1157
1996
  const resolveSessionIdBySdk = (sdkSessionId) => this.findSessionIdBySdkSession(sdkSessionId);
1158
- const workerClientFactory = dependencies?.createWorkerClient ?? ((mcpServers) => new AIClient({
1997
+ const workerClientFactory = dependencies?.createWorkerClient ?? ((mcpServers, cwd) => new AIClient({
1159
1998
  defaults: {
1160
1999
  allowedTools: [...BASE_ALLOWED_WORKER_TOOLS, "mcp__worker_status__send_status_update"],
1161
- mcpServers
2000
+ mcpServers,
2001
+ cwd: cwd || process.cwd()
1162
2002
  }
1163
2003
  }));
1164
2004
  this.workerManager = dependencies?.workerManager ?? new WorkerManager({
1165
2005
  createClient: workerClientFactory,
1166
- resolveSessionIdBySdk
2006
+ resolveSessionIdBySdk,
2007
+ repoPath: dependencies?.repoPath ?? process.cwd()
1167
2008
  });
1168
2009
  const workerMcpServer = createWorkerMcpServer(this.workerManager, {
1169
2010
  resolveSessionIdBySdk,
@@ -1173,11 +2014,16 @@ var AgentOrchestrator = class {
1173
2014
  });
1174
2015
  const mainClientFactory = dependencies?.createMainClient ?? (() => new AIClient({
1175
2016
  defaults: {
1176
- allowedTools: [...BASE_ALLOWED_ORCHESTRATOR_TOOLS, WORKER_LAUNCH_TOOL, WORKER_GET_TOOL, WORKER_MESSAGE_TOOL],
2017
+ allowedTools: [
2018
+ ...BASE_ALLOWED_ORCHESTRATOR_TOOLS,
2019
+ WORKER_LAUNCH_TOOL,
2020
+ WORKER_GET_TOOL,
2021
+ WORKER_MESSAGE_TOOL
2022
+ ],
1177
2023
  mcpServers: {
1178
2024
  worker: workerMcpServer
1179
2025
  },
1180
- systemPrompt: orchestrator_prompt_default
2026
+ systemPrompt: ORCHESTRATOR_SYSTEM_PROMPT
1181
2027
  }
1182
2028
  }));
1183
2029
  this.aiClient = mainClientFactory();
@@ -1247,6 +2093,7 @@ var AgentOrchestrator = class {
1247
2093
  this.started = true;
1248
2094
  }
1249
2095
  async stop() {
2096
+ await this.workerManager.shutdown();
1250
2097
  for (const connection of Array.from(this.clients.values())) {
1251
2098
  this.handleDisconnect(connection);
1252
2099
  try {
@@ -1648,7 +2495,12 @@ var AgentOrchestrator = class {
1648
2495
  metadata,
1649
2496
  messageId: randomUUID3()
1650
2497
  });
1651
- session.logInbound({ type: "worker_result", workerId: record.id, workerTask: record.task, summary });
2498
+ session.logInbound({
2499
+ type: "worker_result",
2500
+ workerId: record.id,
2501
+ workerTask: record.task,
2502
+ summary
2503
+ });
1652
2504
  session.announceInboundMessage(summary, "worker", record.id, record.task);
1653
2505
  const statusMessage = this.buildWorkerStatusMessage(record.id);
1654
2506
  session.enqueueUserMessage(statusMessage, {
@@ -1744,76 +2596,6 @@ var AgentOrchestrator = class {
1744
2596
  }
1745
2597
  };
1746
2598
 
1747
- // src/auth/credentials-store.ts
1748
- import fs3 from "fs/promises";
1749
- import os2 from "os";
1750
- import path2 from "path";
1751
- var DEFAULT_DIR = path2.join(os2.homedir(), ".omnara");
1752
- var DEFAULT_CREDENTIALS_PATH = path2.join(DEFAULT_DIR, "creds.json");
1753
- var DEFAULT_FILE_MODE = 384;
1754
- var DEFAULT_DIR_MODE = 448;
1755
- function isStoredCredentials(value) {
1756
- return Boolean(value) && typeof value === "object";
1757
- }
1758
- var CredentialsStore = class {
1759
- constructor(filePath = DEFAULT_CREDENTIALS_PATH) {
1760
- this.filePath = filePath;
1761
- }
1762
- async load() {
1763
- try {
1764
- const raw = await fs3.readFile(this.filePath, "utf8");
1765
- const parsed = JSON.parse(raw);
1766
- if (!isStoredCredentials(parsed)) {
1767
- throw new Error("File does not contain valid credentials data");
1768
- }
1769
- return parsed;
1770
- } catch (error) {
1771
- if (isFileNotFound(error)) {
1772
- return null;
1773
- }
1774
- throw new Error(
1775
- `Failed to read credentials from ${this.filePath}: ${extractErrorMessage2(error)}`
1776
- );
1777
- }
1778
- }
1779
- async save(credentials) {
1780
- const payload = {
1781
- accessToken: credentials.accessToken,
1782
- accessTokenExpiresAt: credentials.accessTokenExpiresAt,
1783
- refreshToken: credentials.refreshToken,
1784
- refreshTokenExpiresAt: credentials.refreshTokenExpiresAt
1785
- };
1786
- await fs3.mkdir(path2.dirname(this.filePath), { recursive: true, mode: DEFAULT_DIR_MODE });
1787
- const tempPath = `${this.filePath}.tmp`;
1788
- const serialized = JSON.stringify(payload, null, 2);
1789
- await fs3.writeFile(tempPath, serialized, { mode: DEFAULT_FILE_MODE });
1790
- await fs3.chmod(tempPath, DEFAULT_FILE_MODE);
1791
- await fs3.rename(tempPath, this.filePath);
1792
- }
1793
- async clear() {
1794
- try {
1795
- await fs3.unlink(this.filePath);
1796
- } catch (error) {
1797
- if (!isFileNotFound(error)) {
1798
- throw new Error(
1799
- `Failed to remove credentials file ${this.filePath}: ${extractErrorMessage2(error)}`
1800
- );
1801
- }
1802
- }
1803
- }
1804
- };
1805
- function isFileNotFound(error) {
1806
- return Boolean(
1807
- error && typeof error === "object" && "code" in error && error.code === "ENOENT"
1808
- );
1809
- }
1810
- function extractErrorMessage2(error) {
1811
- if (error instanceof Error && error.message) {
1812
- return error.message;
1813
- }
1814
- return String(error);
1815
- }
1816
-
1817
2599
  // src/relay/relay-bridge.ts
1818
2600
  import { randomUUID as randomUUID4 } from "crypto";
1819
2601
  import EventEmitter2 from "events";
@@ -1834,8 +2616,8 @@ var ApiClient = class {
1834
2616
  constructor(options) {
1835
2617
  this.baseUrl = normalizeBaseUrl(options.baseUrl);
1836
2618
  }
1837
- async getJson(path5, options) {
1838
- const url = resolveUrl(this.baseUrl, path5);
2619
+ async getJson(path7, options) {
2620
+ const url = resolveUrl(this.baseUrl, path7);
1839
2621
  const headers = this.buildHeaders(options);
1840
2622
  const response = await fetch(url, {
1841
2623
  method: "GET",
@@ -1844,8 +2626,8 @@ var ApiClient = class {
1844
2626
  });
1845
2627
  return await this.handleResponse(response);
1846
2628
  }
1847
- async postJson(path5, body, options) {
1848
- const url = resolveUrl(this.baseUrl, path5);
2629
+ async postJson(path7, body, options) {
2630
+ const url = resolveUrl(this.baseUrl, path7);
1849
2631
  const headers = this.buildHeaders(options, { includeJson: true });
1850
2632
  const response = await fetch(url, {
1851
2633
  method: "POST",
@@ -1883,7 +2665,7 @@ var ApiClient = class {
1883
2665
  if (response.ok) {
1884
2666
  throw new ApiError(
1885
2667
  response.status,
1886
- `Failed to parse JSON response: ${extractErrorMessage3(error)}`
2668
+ `Failed to parse JSON response: ${extractErrorMessage4(error)}`
1887
2669
  );
1888
2670
  }
1889
2671
  }
@@ -1903,12 +2685,12 @@ function normalizeBaseUrl(raw) {
1903
2685
  }
1904
2686
  return raw.endsWith("/") ? raw : `${raw}/`;
1905
2687
  }
1906
- function resolveUrl(base, path5) {
2688
+ function resolveUrl(base, path7) {
1907
2689
  const baseUrl = new URL(base);
1908
- if (!path5.startsWith("/")) {
1909
- path5 = `/${path5}`;
2690
+ if (!path7.startsWith("/")) {
2691
+ path7 = `/${path7}`;
1910
2692
  }
1911
- const resolved = new URL(path5, baseUrl);
2693
+ const resolved = new URL(path7, baseUrl);
1912
2694
  return resolved.toString();
1913
2695
  }
1914
2696
  function extractDetail(payload) {
@@ -1931,7 +2713,7 @@ function extractDetail(payload) {
1931
2713
  }
1932
2714
  return null;
1933
2715
  }
1934
- function extractErrorMessage3(error) {
2716
+ function extractErrorMessage4(error) {
1935
2717
  if (error instanceof Error) {
1936
2718
  return error.message;
1937
2719
  }
@@ -2212,7 +2994,11 @@ var LoopbackListener = class _LoopbackListener {
2212
2994
  const sessionId = url.searchParams.get("session");
2213
2995
  const state = url.searchParams.get("state");
2214
2996
  if (!code) {
2215
- this.respond(response, 400, buildMessageHtml("Missing authorization code. Return to your CLI and try again."));
2997
+ this.respond(
2998
+ response,
2999
+ 400,
3000
+ buildMessageHtml("Missing authorization code. Return to your CLI and try again.")
3001
+ );
2216
3002
  return;
2217
3003
  }
2218
3004
  this.respond(response, 200, buildSuccessHtml());
@@ -2445,14 +3231,14 @@ function reportLoopbackError(error) {
2445
3231
  );
2446
3232
  return;
2447
3233
  }
2448
- console.log(`Automatic login failed: ${extractErrorMessage4(error)}`);
3234
+ console.log(`Automatic login failed: ${extractErrorMessage5(error)}`);
2449
3235
  }
2450
3236
  function printSessionInstructions(session) {
2451
3237
  console.log("\nOpen this link in your browser if it did not open automatically:");
2452
3238
  console.log(` ${session.verificationUriComplete}`);
2453
3239
  console.log("Approve the request, then return to the CLI.");
2454
3240
  }
2455
- function extractErrorMessage4(error) {
3241
+ function extractErrorMessage5(error) {
2456
3242
  if (error instanceof Error && error.message) {
2457
3243
  return error.message;
2458
3244
  }
@@ -2475,7 +3261,7 @@ async function waitForLoopback(listener, sessionId, onCode) {
2475
3261
  reportLoopbackError(error);
2476
3262
  }
2477
3263
  } catch (error) {
2478
- console.log(`Loopback listener error: ${extractErrorMessage4(error)}`);
3264
+ console.log(`Loopback listener error: ${extractErrorMessage5(error)}`);
2479
3265
  }
2480
3266
  }
2481
3267
 
@@ -4423,6 +5209,58 @@ var api_billing_subscription_response_schema_default = {
4423
5209
  ],
4424
5210
  default: null,
4425
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"
4426
5264
  }
4427
5265
  },
4428
5266
  required: [
@@ -4631,6 +5469,77 @@ var api_billing_promo_validation_response_schema_default = {
4631
5469
  type: "object"
4632
5470
  };
4633
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: {
5523
+ anyOf: [
5524
+ {
5525
+ format: "date-time",
5526
+ type: "string"
5527
+ },
5528
+ {
5529
+ type: "null"
5530
+ }
5531
+ ],
5532
+ default: null,
5533
+ description: "When the portal session expires (if provided by Stripe).",
5534
+ title: "Expires At"
5535
+ }
5536
+ },
5537
+ required: [
5538
+ "url"
5539
+ ],
5540
+ type: "object"
5541
+ };
5542
+
4634
5543
  // ../shared-js/schemas/api_settings_notification_request.schema.json
4635
5544
  var api_settings_notification_request_schema_default = {
4636
5545
  $schema: "https://json-schema.org/draft/2020-12/schema",
@@ -4766,6 +5675,8 @@ var validateCreateCheckoutSessionRequest = ajv.compile(api_billing_create_checko
4766
5675
  var validateCheckoutSessionResponse = ajv.compile(api_billing_checkout_response_schema_default);
4767
5676
  var validateValidatePromoCodeRequest = ajv.compile(api_billing_validate_promo_request_schema_default);
4768
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);
4769
5680
  var validateUserNotificationSettingsRequest = ajv.compile(api_settings_notification_request_schema_default);
4770
5681
  var validateUserNotificationSettingsResponse = ajv.compile(api_settings_notification_response_schema_default);
4771
5682
 
@@ -4820,7 +5731,7 @@ var RelayBridge = class extends EventEmitter2 {
4820
5731
  break;
4821
5732
  }
4822
5733
  this.logger.error(
4823
- `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.`
4824
5735
  );
4825
5736
  await delay(backoffMs);
4826
5737
  backoffMs = Math.min(backoffMs * 2, 3e4);
@@ -4943,7 +5854,7 @@ var RelayBridge = class extends EventEmitter2 {
4943
5854
  target.send(serialized);
4944
5855
  return;
4945
5856
  } catch (error) {
4946
- this.logger.error(`Failed to send relay frame: ${extractErrorMessage5(error)}`);
5857
+ this.logger.error(`Failed to send relay frame: ${extractErrorMessage6(error)}`);
4947
5858
  if (target === this.ws) {
4948
5859
  this.closeCurrentSocket();
4949
5860
  }
@@ -5002,7 +5913,7 @@ var RelayBridge = class extends EventEmitter2 {
5002
5913
  const text = typeof data === "string" ? data : data.toString("utf8");
5003
5914
  parsed = JSON.parse(text);
5004
5915
  } catch (error) {
5005
- this.logger.error(`Received invalid JSON from relay: ${extractErrorMessage5(error)}`);
5916
+ this.logger.error(`Received invalid JSON from relay: ${extractErrorMessage6(error)}`);
5006
5917
  return;
5007
5918
  }
5008
5919
  if (this.tryHandleControlMessage(parsed)) {
@@ -5032,6 +5943,8 @@ var RelayBridge = class extends EventEmitter2 {
5032
5943
  }
5033
5944
  return frame;
5034
5945
  }
5946
+ case "user_text":
5947
+ return this.buildUserTextEnvelope(message);
5035
5948
  case "assistant_text":
5036
5949
  return this.buildTextEnvelope(message);
5037
5950
  case "tool_call":
@@ -5177,6 +6090,30 @@ var RelayBridge = class extends EventEmitter2 {
5177
6090
  isTextContent(content) {
5178
6091
  return typeof content.text === "string";
5179
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
+ }
5180
6117
  buildTextEnvelope(message) {
5181
6118
  const content = {
5182
6119
  type: "text",
@@ -5344,7 +6281,7 @@ var RelayBridge = class extends EventEmitter2 {
5344
6281
  try {
5345
6282
  this.ws.send(payload);
5346
6283
  } catch (error) {
5347
- this.logger.error(`Failed to flush relay queue: ${extractErrorMessage5(error)}`);
6284
+ this.logger.error(`Failed to flush relay queue: ${extractErrorMessage6(error)}`);
5348
6285
  this.pendingOutbound.unshift(payload);
5349
6286
  this.closeCurrentSocket();
5350
6287
  break;
@@ -5375,23 +6312,23 @@ var RelayBridge = class extends EventEmitter2 {
5375
6312
  }
5376
6313
  }
5377
6314
  };
5378
- function buildRelayWebsocketUrl(baseUrl, path5) {
6315
+ function buildRelayWebsocketUrl(baseUrl, path7) {
5379
6316
  const url = new URL(baseUrl);
5380
6317
  url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
5381
6318
  url.search = "";
5382
6319
  url.hash = "";
5383
- if (path5.startsWith("/")) {
5384
- url.pathname = path5;
6320
+ if (path7.startsWith("/")) {
6321
+ url.pathname = path7;
5385
6322
  } else {
5386
6323
  const basePath = url.pathname.endsWith("/") ? url.pathname : `${url.pathname}/`;
5387
- url.pathname = `${basePath}${path5}`;
6324
+ url.pathname = `${basePath}${path7}`;
5388
6325
  }
5389
6326
  return url.toString();
5390
6327
  }
5391
6328
  function delay(ms) {
5392
6329
  return new Promise((resolve) => setTimeout(resolve, ms));
5393
6330
  }
5394
- function extractErrorMessage5(error) {
6331
+ function extractErrorMessage6(error) {
5395
6332
  if (error instanceof Error) {
5396
6333
  return error.message;
5397
6334
  }
@@ -5595,13 +6532,17 @@ function handleOutboundMessage(message, onPrompt, onSessionChange, onTurnComplet
5595
6532
  }
5596
6533
  switch (message.type) {
5597
6534
  case "session_created":
5598
- console.log(`
5599
- ${formatSessionPrefix(message.sessionId)} [server] Session created: ${message.sessionId}`);
6535
+ console.log(
6536
+ `
6537
+ ${formatSessionPrefix(message.sessionId)} [server] Session created: ${message.sessionId}`
6538
+ );
5600
6539
  onSessionChange(message.sessionId);
5601
6540
  break;
5602
6541
  case "session_joined":
5603
- console.log(`
5604
- ${formatSessionPrefix(message.sessionId)} [server] Joined session: ${message.sessionId}`);
6542
+ console.log(
6543
+ `
6544
+ ${formatSessionPrefix(message.sessionId)} [server] Joined session: ${message.sessionId}`
6545
+ );
5605
6546
  onSessionChange(message.sessionId);
5606
6547
  break;
5607
6548
  case "session_init":
@@ -5679,10 +6620,8 @@ ${formatSessionPrefix(message.sessionId)} [system] ${message.subtype}`);
5679
6620
  }
5680
6621
  break;
5681
6622
  case "error":
5682
- console.error(
5683
- `
5684
- ${formatSessionPrefix(message.sessionId)} [error] ${message.message}`
5685
- );
6623
+ console.error(`
6624
+ ${formatSessionPrefix(message.sessionId)} [error] ${message.message}`);
5686
6625
  onTurnComplete();
5687
6626
  break;
5688
6627
  case "pong":
@@ -5725,7 +6664,7 @@ Commands:
5725
6664
 
5726
6665
  // src/voice/voice-service.ts
5727
6666
  async function launchVoiceForSession(client, accessToken, options) {
5728
- const path5 = `/api/v1/voice/sessions/${options.sessionId}/launch`;
6667
+ const path7 = `/api/v1/voice/sessions/${options.sessionId}/launch`;
5729
6668
  const requestBody = {};
5730
6669
  if (options.force) {
5731
6670
  requestBody.force = true;
@@ -5734,7 +6673,7 @@ async function launchVoiceForSession(client, accessToken, options) {
5734
6673
  requestBody.room_name = options.roomName;
5735
6674
  }
5736
6675
  const response = await client.postJson(
5737
- path5,
6676
+ path7,
5738
6677
  requestBody,
5739
6678
  { accessToken }
5740
6679
  );
@@ -5759,278 +6698,52 @@ function checkClaudeCodeInstallation() {
5759
6698
  try {
5760
6699
  execSync("claude --version", {
5761
6700
  stdio: "ignore",
5762
- encoding: "utf-8"
6701
+ encoding: "utf-8",
6702
+ env: process.env
5763
6703
  });
5764
6704
  return true;
5765
- } catch (error) {
6705
+ } catch {
5766
6706
  return false;
5767
6707
  }
5768
6708
  }
6709
+ function checkAnthropicApiKey() {
6710
+ return !!process.env.ANTHROPIC_API_KEY && process.env.ANTHROPIC_API_KEY.trim().length > 0;
6711
+ }
5769
6712
  function displayClaudeCodeWarning() {
5770
- console.log(chalk.yellow("\nWarning: Claude Code is not installed or not found in PATH"));
5771
- console.log(chalk.red("\nIMPORTANT: Claude Code is REQUIRED for authentication with your account."));
5772
- console.log(chalk.white("\nClaude Code provides:"));
5773
- console.log(chalk.gray(" - AI-powered code assistance"));
5774
- console.log(chalk.gray(" - Intelligent code completion"));
5775
- console.log(chalk.gray(" - Natural language to code translation"));
5776
- console.log(chalk.gray(" - User authentication for API access"));
5777
- console.log(chalk.white("\nTo install Claude Code:"));
5778
- console.log(chalk.cyan(" 1. Visit: https://claude.ai/download"));
5779
- console.log(chalk.cyan(" 2. Download and install Claude Code for your platform"));
5780
- console.log(chalk.cyan(' 3. Make sure the "claude" command is available in your PATH'));
5781
- console.log(chalk.yellow("\nNote: Without Claude Code, you cannot authenticate and access"));
5782
- console.log(chalk.yellow("personalized features that require your user credentials.\n"));
6713
+ console.log(
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
+ )
6717
+ );
5783
6718
  }
5784
6719
  function performClaudeCodeCheck(options = {}) {
5785
6720
  const { silent = false, exitOnMissing = false } = options;
5786
6721
  if (process.env.OMNARA_SKIP_CLAUDE_CHECK === "true") {
5787
6722
  return true;
5788
6723
  }
5789
- const isInstalled = checkClaudeCodeInstallation();
5790
- if (!isInstalled && !silent) {
6724
+ const isClaudeInstalled = checkClaudeCodeInstallation();
6725
+ const hasApiKey = checkAnthropicApiKey();
6726
+ const hasAuthentication = isClaudeInstalled || hasApiKey;
6727
+ if (!hasAuthentication && !silent) {
5791
6728
  displayClaudeCodeWarning();
5792
6729
  if (exitOnMissing) {
5793
- console.error(chalk.red("Exiting: Claude Code is required for this operation."));
6730
+ console.error(chalk.red("Exiting: Authentication is required for this operation."));
5794
6731
  process.exit(1);
5795
6732
  }
5796
6733
  }
5797
- return isInstalled;
6734
+ return hasAuthentication;
5798
6735
  }
5799
6736
  function getClaudeCodeVersion() {
5800
6737
  try {
5801
6738
  const version = execSync("claude --version", {
5802
6739
  encoding: "utf-8",
5803
- stdio: ["ignore", "pipe", "ignore"]
6740
+ stdio: ["ignore", "pipe", "ignore"],
6741
+ env: process.env
5804
6742
  }).trim();
5805
6743
  return version;
5806
- } catch (error) {
5807
- return null;
5808
- }
5809
- }
5810
-
5811
- // src/utils/logger.ts
5812
- import path3 from "path";
5813
- function isProduction() {
5814
- if (process.env.NODE_ENV === "production") {
5815
- return true;
5816
- }
5817
- try {
5818
- const currentFile = typeof __filename !== "undefined" ? __filename : "";
5819
- const isInDist = currentFile.includes(path3.sep + "dist" + path3.sep);
5820
- if (isInDist && process.env.NODE_ENV !== "development") {
5821
- return true;
5822
- }
5823
6744
  } catch {
6745
+ return null;
5824
6746
  }
5825
- const logLevel = process.env.OMNARA_LOG_LEVEL?.toLowerCase();
5826
- if (logLevel === "none" || logLevel === "silent") {
5827
- return true;
5828
- }
5829
- return false;
5830
- }
5831
- function getLogLevel() {
5832
- const envLevel = process.env.OMNARA_LOG_LEVEL?.toLowerCase();
5833
- switch (envLevel) {
5834
- case "none":
5835
- case "silent":
5836
- return 0 /* NONE */;
5837
- case "error":
5838
- return 1 /* ERROR */;
5839
- case "warn":
5840
- case "warning":
5841
- return 2 /* WARN */;
5842
- case "info":
5843
- return 3 /* INFO */;
5844
- case "debug":
5845
- return 4 /* DEBUG */;
5846
- case "verbose":
5847
- case "trace":
5848
- return 5 /* VERBOSE */;
5849
- default:
5850
- return isProduction() ? 1 /* ERROR */ : 3 /* INFO */;
5851
- }
5852
- }
5853
- var ControlledLogger = class {
5854
- logLevel;
5855
- forceOutput;
5856
- _console;
5857
- // Console properties we need to implement
5858
- Console;
5859
- constructor(options) {
5860
- this.logLevel = options?.logLevel ?? getLogLevel();
5861
- this.forceOutput = options?.forceOutput ?? false;
5862
- this._console = console;
5863
- this.Console = console.Console;
5864
- }
5865
- /**
5866
- * Standard log output (INFO level)
5867
- */
5868
- log(...args) {
5869
- if (this.shouldLog(3 /* INFO */)) {
5870
- console.log(...args);
5871
- }
5872
- }
5873
- /**
5874
- * Error output (always shown unless NONE)
5875
- */
5876
- error(...args) {
5877
- if (this.shouldLog(1 /* ERROR */)) {
5878
- console.error(...args);
5879
- }
5880
- }
5881
- /**
5882
- * Warning output
5883
- */
5884
- warn(...args) {
5885
- if (this.shouldLog(2 /* WARN */)) {
5886
- console.warn(...args);
5887
- }
5888
- }
5889
- /**
5890
- * Info level output
5891
- */
5892
- info(...args) {
5893
- if (this.shouldLog(3 /* INFO */)) {
5894
- console.info(...args);
5895
- }
5896
- }
5897
- /**
5898
- * Debug output (only in development)
5899
- */
5900
- debug(...args) {
5901
- if (this.shouldLog(4 /* DEBUG */)) {
5902
- console.debug(...args);
5903
- }
5904
- }
5905
- /**
5906
- * Verbose/trace output
5907
- */
5908
- trace(...args) {
5909
- if (this.shouldLog(5 /* VERBOSE */)) {
5910
- console.log("[TRACE]", ...args);
5911
- }
5912
- }
5913
- /**
5914
- * Important messages that should always be shown to user
5915
- * (authentication status, connection info, etc.)
5916
- */
5917
- important(...args) {
5918
- if (this.logLevel !== 0 /* NONE */ || this.forceOutput) {
5919
- console.log(...args);
5920
- }
5921
- }
5922
- /**
5923
- * Success messages that should typically be shown
5924
- */
5925
- success(...args) {
5926
- if (this.shouldLog(2 /* WARN */) || this.forceOutput) {
5927
- console.log(...args);
5928
- }
5929
- }
5930
- /**
5931
- * Check if a message should be logged at the given level
5932
- */
5933
- shouldLog(level) {
5934
- return this.forceOutput || this.logLevel >= level;
5935
- }
5936
- /**
5937
- * Update the log level dynamically
5938
- */
5939
- setLogLevel(level) {
5940
- this.logLevel = level;
5941
- }
5942
- /**
5943
- * Get current log level
5944
- */
5945
- getLogLevel() {
5946
- return this.logLevel;
5947
- }
5948
- // Console methods we delegate or control based on log level
5949
- assert(condition, ...data) {
5950
- if (this.shouldLog(1 /* ERROR */)) {
5951
- this._console.assert(condition, ...data);
5952
- }
5953
- }
5954
- clear() {
5955
- this._console.clear();
5956
- }
5957
- count(label) {
5958
- if (this.shouldLog(4 /* DEBUG */)) {
5959
- this._console.count(label);
5960
- }
5961
- }
5962
- countReset(label) {
5963
- if (this.shouldLog(4 /* DEBUG */)) {
5964
- this._console.countReset(label);
5965
- }
5966
- }
5967
- dir(item, options) {
5968
- if (this.shouldLog(4 /* DEBUG */)) {
5969
- this._console.dir(item, options);
5970
- }
5971
- }
5972
- dirxml(...data) {
5973
- if (this.shouldLog(4 /* DEBUG */)) {
5974
- this._console.dirxml(...data);
5975
- }
5976
- }
5977
- group(...data) {
5978
- if (this.shouldLog(4 /* DEBUG */)) {
5979
- this._console.group(...data);
5980
- }
5981
- }
5982
- groupCollapsed(...data) {
5983
- if (this.shouldLog(4 /* DEBUG */)) {
5984
- this._console.groupCollapsed(...data);
5985
- }
5986
- }
5987
- groupEnd() {
5988
- if (this.shouldLog(4 /* DEBUG */)) {
5989
- this._console.groupEnd();
5990
- }
5991
- }
5992
- table(tabularData, properties) {
5993
- if (this.shouldLog(3 /* INFO */)) {
5994
- this._console.table(tabularData, properties);
5995
- }
5996
- }
5997
- time(label) {
5998
- if (this.shouldLog(4 /* DEBUG */)) {
5999
- this._console.time(label);
6000
- }
6001
- }
6002
- timeEnd(label) {
6003
- if (this.shouldLog(4 /* DEBUG */)) {
6004
- this._console.timeEnd(label);
6005
- }
6006
- }
6007
- timeLog(label, ...data) {
6008
- if (this.shouldLog(4 /* DEBUG */)) {
6009
- this._console.timeLog(label, ...data);
6010
- }
6011
- }
6012
- timeStamp(label) {
6013
- if (this.shouldLog(4 /* DEBUG */)) {
6014
- this._console.timeStamp(label);
6015
- }
6016
- }
6017
- profile(label) {
6018
- if (this.shouldLog(4 /* DEBUG */)) {
6019
- this._console.profile?.(label);
6020
- }
6021
- }
6022
- profileEnd(label) {
6023
- if (this.shouldLog(4 /* DEBUG */)) {
6024
- this._console.profileEnd?.(label);
6025
- }
6026
- }
6027
- };
6028
- var logger = new ControlledLogger();
6029
- function extractErrorMessage6(error) {
6030
- if (error instanceof Error) {
6031
- return error.message;
6032
- }
6033
- return String(error);
6034
6747
  }
6035
6748
 
6036
6749
  // src/commands/agent.ts
@@ -6077,7 +6790,7 @@ async function runAgentOrchestrator(options = {}) {
6077
6790
  try {
6078
6791
  await bridge.start();
6079
6792
  } catch (error) {
6080
- logger.error(`Relay orchestrator terminated: ${extractErrorMessage6(error)}`);
6793
+ logger.error(`Relay orchestrator terminated: ${extractErrorMessage2(error)}`);
6081
6794
  process.exitCode = process.exitCode || 1;
6082
6795
  } finally {
6083
6796
  await stop();
@@ -6162,16 +6875,16 @@ async function runVoice(options) {
6162
6875
  logger.debug(launch.relayToken);
6163
6876
  logger.important("\nKeep this information secure\u2014tokens grant access to your agent session.");
6164
6877
  }
6165
- function buildRelayWebsocketUrl2(baseUrl, path5) {
6878
+ function buildRelayWebsocketUrl2(baseUrl, path7) {
6166
6879
  const url = new URL(baseUrl);
6167
6880
  url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
6168
6881
  url.search = "";
6169
6882
  url.hash = "";
6170
- if (path5.startsWith("/")) {
6171
- url.pathname = path5;
6883
+ if (path7.startsWith("/")) {
6884
+ url.pathname = path7;
6172
6885
  } else {
6173
6886
  const basePath = url.pathname.endsWith("/") ? url.pathname : `${url.pathname}/`;
6174
- url.pathname = `${basePath}${path5}`;
6887
+ url.pathname = `${basePath}${path7}`;
6175
6888
  }
6176
6889
  return url.toString();
6177
6890
  }
@@ -6344,9 +7057,9 @@ function isMainModule() {
6344
7057
  if (!import.meta.url || !process.argv[1]) {
6345
7058
  return false;
6346
7059
  }
6347
- const executedPath = path4.resolve(process.argv[1]);
7060
+ const executedPath = path6.resolve(process.argv[1]);
6348
7061
  const currentPath = import.meta.url.startsWith("file://") ? decodeURIComponent(import.meta.url.slice(7)) : import.meta.url;
6349
- return path4.normalize(currentPath) === path4.normalize(executedPath);
7062
+ return path6.normalize(currentPath) === path6.normalize(executedPath);
6350
7063
  }
6351
7064
  if (isMainModule()) {
6352
7065
  run(process.argv).catch((error) => {