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/auth/credentials-store.d.ts +1 -0
- package/dist/cli.js +1127 -414
- package/dist/cli.js.map +4 -4
- package/dist/config/omnara-config.d.ts +34 -0
- package/dist/index.d.ts +1 -0
- package/dist/orchestrator/agent-orchestrator.d.ts +3 -1
- package/dist/orchestrator/orchestrator-prompt.d.ts +1 -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 +518 -0
- package/dist/relay/relay-bridge.d.ts +1 -0
- package/dist/schemas/index.d.ts +1 -1
- package/dist/utils/claude-code-check.d.ts +6 -2
- package/dist/utils/logger.d.ts +18 -17
- package/package.json +5 -13
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// ESM shim for __filename and __dirname
|
|
2
|
-
import { fileURLToPath } from 'url';
|
|
3
|
-
import
|
|
4
|
-
const __filename =
|
|
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
|
|
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, {
|
|
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
|
-
|
|
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
|
|
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
|
-
* @
|
|
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
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
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
|
-
|
|
738
|
-
|
|
739
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
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:
|
|
997
|
-
|
|
998
|
-
|
|
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(
|
|
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:
|
|
1086
|
-
message:
|
|
1087
|
-
|
|
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
|
|
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.
|
|
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: [
|
|
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:
|
|
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({
|
|
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(
|
|
1838
|
-
const url = resolveUrl(this.baseUrl,
|
|
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(
|
|
1848
|
-
const url = resolveUrl(this.baseUrl,
|
|
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: ${
|
|
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,
|
|
2688
|
+
function resolveUrl(base, path7) {
|
|
1907
2689
|
const baseUrl = new URL(base);
|
|
1908
|
-
if (!
|
|
1909
|
-
|
|
2690
|
+
if (!path7.startsWith("/")) {
|
|
2691
|
+
path7 = `/${path7}`;
|
|
1910
2692
|
}
|
|
1911
|
-
const resolved = new URL(
|
|
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
|
|
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(
|
|
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: ${
|
|
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
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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,
|
|
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 (
|
|
5384
|
-
url.pathname =
|
|
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}${
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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(
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
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
|
|
5790
|
-
|
|
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:
|
|
6730
|
+
console.error(chalk.red("Exiting: Authentication is required for this operation."));
|
|
5794
6731
|
process.exit(1);
|
|
5795
6732
|
}
|
|
5796
6733
|
}
|
|
5797
|
-
return
|
|
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: ${
|
|
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,
|
|
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 (
|
|
6171
|
-
url.pathname =
|
|
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}${
|
|
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 =
|
|
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
|
|
7062
|
+
return path6.normalize(currentPath) === path6.normalize(executedPath);
|
|
6350
7063
|
}
|
|
6351
7064
|
if (isMainModule()) {
|
|
6352
7065
|
run(process.argv).catch((error) => {
|