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