omnara 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +830 -148
- package/dist/cli.js.map +4 -4
- package/dist/config/omnara-config.d.ts +5 -0
- package/dist/orchestrator/agent-orchestrator.d.ts +7 -1
- package/dist/orchestrator/ai-client.d.ts +3 -0
- package/dist/orchestrator/orchestrator-prompt.md +171 -43
- package/dist/orchestrator/utils/completions-manager.d.ts +8 -0
- package/dist/orchestrator/utils/worktree-manager.d.ts +28 -0
- package/dist/orchestrator/worker-manager.d.ts +2 -5
- package/dist/orchestrator-prompt.md +171 -43
- package/dist/relay/relay-bridge.d.ts +4 -0
- package/package.json +4 -2
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ const __filename = __omnaraFileURLToPath(import.meta.url);
|
|
|
5
5
|
const __dirname = pathShim.dirname(__filename);
|
|
6
6
|
|
|
7
7
|
// src/cli.ts
|
|
8
|
-
import
|
|
8
|
+
import path7 from "path";
|
|
9
9
|
import { createRequire as createRequire2 } from "node:module";
|
|
10
10
|
import { Command as Command3 } from "commander";
|
|
11
11
|
|
|
@@ -51,13 +51,15 @@ var BASE_ALLOWED_WORKER_TOOLS = [
|
|
|
51
51
|
];
|
|
52
52
|
var AIClient = class {
|
|
53
53
|
baseOptions;
|
|
54
|
+
runningQuery;
|
|
54
55
|
constructor(config) {
|
|
55
56
|
const defaultOptions = {
|
|
56
57
|
maxTurns: 100,
|
|
57
58
|
model: "sonnet",
|
|
58
59
|
cwd: process.cwd(),
|
|
59
60
|
includePartialMessages: false,
|
|
60
|
-
allowedTools: [...BASE_ALLOWED_ORCHESTRATOR_TOOLS]
|
|
61
|
+
allowedTools: [...BASE_ALLOWED_ORCHESTRATOR_TOOLS],
|
|
62
|
+
settingSources: ["user", "project", "local"]
|
|
61
63
|
};
|
|
62
64
|
this.baseOptions = {
|
|
63
65
|
...defaultOptions,
|
|
@@ -70,9 +72,11 @@ var AIClient = class {
|
|
|
70
72
|
}
|
|
71
73
|
async *streamUserMessage(prompt2, overrides) {
|
|
72
74
|
const q = this.createQuery(prompt2, overrides);
|
|
75
|
+
this.runningQuery = q;
|
|
73
76
|
for await (const message of q) {
|
|
74
77
|
yield message;
|
|
75
78
|
}
|
|
79
|
+
this.runningQuery = void 0;
|
|
76
80
|
}
|
|
77
81
|
createQuery(prompt2, overrides) {
|
|
78
82
|
const merged = {
|
|
@@ -85,6 +89,9 @@ var AIClient = class {
|
|
|
85
89
|
options: merged
|
|
86
90
|
});
|
|
87
91
|
}
|
|
92
|
+
async cancel() {
|
|
93
|
+
await this.runningQuery?.interrupt();
|
|
94
|
+
}
|
|
88
95
|
ensureWorkingDirectory(cwd) {
|
|
89
96
|
if (!cwd) {
|
|
90
97
|
return;
|
|
@@ -433,6 +440,7 @@ function extractUserContent(content) {
|
|
|
433
440
|
// src/orchestrator/worker-manager.ts
|
|
434
441
|
import { EventEmitter } from "events";
|
|
435
442
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
443
|
+
import { simpleGit as simpleGit2 } from "simple-git";
|
|
436
444
|
|
|
437
445
|
// src/orchestrator/tools/worker-status-tool.ts
|
|
438
446
|
import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -861,9 +869,16 @@ var WorktreeManager = class {
|
|
|
861
869
|
worktreeBasePath;
|
|
862
870
|
activeWorktrees;
|
|
863
871
|
enabled;
|
|
872
|
+
base_ref;
|
|
873
|
+
// Queue mechanism for sequential worktree creation
|
|
874
|
+
creationQueue = [];
|
|
875
|
+
isProcessingQueue = false;
|
|
876
|
+
// Map to track worktrees by name (not workerId) to enable reuse
|
|
877
|
+
worktreesByName = /* @__PURE__ */ new Map();
|
|
864
878
|
constructor(options) {
|
|
865
879
|
this.repoPath = options.repoPath;
|
|
866
880
|
this.enabled = options.enabled ?? false;
|
|
881
|
+
this.base_ref = options.base_ref;
|
|
867
882
|
const repoName = path4.basename(this.repoPath);
|
|
868
883
|
this.worktreeBasePath = options.worktreeBasePath || path4.join(DEFAULT_DIR, "worktrees", repoName);
|
|
869
884
|
this.git = simpleGit(this.repoPath);
|
|
@@ -872,6 +887,33 @@ var WorktreeManager = class {
|
|
|
872
887
|
this.validateGitRepository();
|
|
873
888
|
}
|
|
874
889
|
}
|
|
890
|
+
/**
|
|
891
|
+
* Find a valid base ref to use when creating new branches
|
|
892
|
+
* Tries in order: configured base_ref, main, master, origin/HEAD
|
|
893
|
+
* @returns The first valid ref found, or undefined if none exist
|
|
894
|
+
*/
|
|
895
|
+
async findValidBaseRef() {
|
|
896
|
+
if (this.base_ref) {
|
|
897
|
+
try {
|
|
898
|
+
await this.git.raw(["rev-parse", "--verify", this.base_ref]);
|
|
899
|
+
logger.info(`Using configured base_ref: ${this.base_ref}`);
|
|
900
|
+
return this.base_ref;
|
|
901
|
+
} catch {
|
|
902
|
+
logger.warn(`Configured base_ref "${this.base_ref}" does not exist, trying fallbacks`);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
const fallbackRefs = ["main", "master", "origin/HEAD"];
|
|
906
|
+
for (const ref of fallbackRefs) {
|
|
907
|
+
try {
|
|
908
|
+
await this.git.raw(["rev-parse", "--verify", ref]);
|
|
909
|
+
logger.info(`Using fallback base_ref: ${ref}`);
|
|
910
|
+
return ref;
|
|
911
|
+
} catch {
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
logger.warn("No valid base ref found (tried: configured base_ref, main, master, origin/HEAD)");
|
|
915
|
+
return void 0;
|
|
916
|
+
}
|
|
875
917
|
/**
|
|
876
918
|
* Validate that the repoPath is a git repository
|
|
877
919
|
* Logs an error if worktrees are enabled but not in a git repository
|
|
@@ -899,6 +941,10 @@ var WorktreeManager = class {
|
|
|
899
941
|
/**
|
|
900
942
|
* Creates a new worktree for the specified worker
|
|
901
943
|
*
|
|
944
|
+
* This method uses a sequential queue to ensure that worktree creation
|
|
945
|
+
* happens one at a time, eliminating race conditions when multiple workers
|
|
946
|
+
* request the same worktree simultaneously.
|
|
947
|
+
*
|
|
902
948
|
* @param workerId - Unique identifier for the worker
|
|
903
949
|
* @param worktreeName - Semantic name for the worktree (e.g., "main-feature", "bugfix-auth")
|
|
904
950
|
* @returns Information about the created worktree
|
|
@@ -912,15 +958,81 @@ var WorktreeManager = class {
|
|
|
912
958
|
"create"
|
|
913
959
|
);
|
|
914
960
|
}
|
|
915
|
-
|
|
961
|
+
if (this.activeWorktrees.has(workerId)) {
|
|
962
|
+
throw new WorktreeError(`Worktree already exists for worker ${workerId}`, workerId, "create");
|
|
963
|
+
}
|
|
964
|
+
return new Promise((resolve, reject) => {
|
|
965
|
+
this.creationQueue.push({
|
|
966
|
+
workerId,
|
|
967
|
+
worktreeName,
|
|
968
|
+
resolve,
|
|
969
|
+
reject
|
|
970
|
+
});
|
|
971
|
+
logger.info(
|
|
972
|
+
`Queued worktree creation for "${worktreeName}" (worker: ${workerId}). Queue size: ${this.creationQueue.length}`
|
|
973
|
+
);
|
|
974
|
+
if (!this.isProcessingQueue) {
|
|
975
|
+
this.processCreationQueue();
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Processes the creation queue sequentially
|
|
981
|
+
* Ensures only one worktree creation happens at a time
|
|
982
|
+
*/
|
|
983
|
+
async processCreationQueue() {
|
|
984
|
+
if (this.isProcessingQueue) {
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
this.isProcessingQueue = true;
|
|
916
988
|
try {
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
989
|
+
while (this.creationQueue.length > 0) {
|
|
990
|
+
const request = this.creationQueue.shift();
|
|
991
|
+
try {
|
|
992
|
+
const existingWorktree = this.worktreesByName.get(request.worktreeName);
|
|
993
|
+
if (existingWorktree) {
|
|
994
|
+
logger.info(
|
|
995
|
+
`Worker ${request.workerId} reusing existing worktree "${request.worktreeName}" at ${existingWorktree.worktreePath}`
|
|
996
|
+
);
|
|
997
|
+
const info = {
|
|
998
|
+
workerId: request.workerId,
|
|
999
|
+
worktreeName: request.worktreeName,
|
|
1000
|
+
worktreePath: existingWorktree.worktreePath,
|
|
1001
|
+
branchName: existingWorktree.branchName,
|
|
1002
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
1003
|
+
};
|
|
1004
|
+
this.activeWorktrees.set(request.workerId, info);
|
|
1005
|
+
request.resolve(info);
|
|
1006
|
+
} else {
|
|
1007
|
+
const info = await this.doCreateWorktree(request.workerId, request.worktreeName);
|
|
1008
|
+
request.resolve(info);
|
|
1009
|
+
}
|
|
1010
|
+
} catch (error) {
|
|
1011
|
+
logger.error(
|
|
1012
|
+
`Failed to process worktree creation for worker ${request.workerId}:`,
|
|
1013
|
+
error
|
|
1014
|
+
);
|
|
1015
|
+
request.reject(
|
|
1016
|
+
error instanceof WorktreeError ? error : new WorktreeError(
|
|
1017
|
+
`Failed to create worktree: ${error instanceof Error ? error.message : String(error)}`,
|
|
1018
|
+
request.workerId,
|
|
1019
|
+
"create",
|
|
1020
|
+
error instanceof Error ? error : void 0
|
|
1021
|
+
)
|
|
1022
|
+
);
|
|
1023
|
+
}
|
|
923
1024
|
}
|
|
1025
|
+
} finally {
|
|
1026
|
+
this.isProcessingQueue = false;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Internal method that performs the actual worktree creation
|
|
1031
|
+
* This should only be called by processCreationQueue
|
|
1032
|
+
*/
|
|
1033
|
+
async doCreateWorktree(workerId, worktreeName) {
|
|
1034
|
+
logger.info(`Creating worktree "${worktreeName}" for worker ${workerId}`);
|
|
1035
|
+
try {
|
|
924
1036
|
await fs4.mkdir(this.worktreeBasePath, { recursive: true });
|
|
925
1037
|
const branchName = `${worktreeName}`;
|
|
926
1038
|
const worktreePath = path4.join(this.worktreeBasePath, worktreeName);
|
|
@@ -948,6 +1060,7 @@ var WorktreeManager = class {
|
|
|
948
1060
|
createdAt: /* @__PURE__ */ new Date()
|
|
949
1061
|
};
|
|
950
1062
|
this.activeWorktrees.set(workerId, info2);
|
|
1063
|
+
this.worktreesByName.set(worktreeName, info2);
|
|
951
1064
|
return info2;
|
|
952
1065
|
}
|
|
953
1066
|
if (worktreeDirExists && !worktreeRegistered) {
|
|
@@ -963,10 +1076,18 @@ var WorktreeManager = class {
|
|
|
963
1076
|
);
|
|
964
1077
|
await this.git.raw(["worktree", "add", worktreePath, branchName]);
|
|
965
1078
|
} else if (!branchExists && !worktreeRegistered) {
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1079
|
+
const baseRef = await this.findValidBaseRef();
|
|
1080
|
+
if (baseRef) {
|
|
1081
|
+
logger.info(
|
|
1082
|
+
`Creating new worktree "${worktreeName}" at ${worktreePath} with new branch ${branchName} from base ref ${baseRef}`
|
|
1083
|
+
);
|
|
1084
|
+
await this.git.raw(["worktree", "add", "-b", branchName, worktreePath, baseRef]);
|
|
1085
|
+
} else {
|
|
1086
|
+
logger.info(
|
|
1087
|
+
`Creating new worktree "${worktreeName}" at ${worktreePath} with new branch ${branchName} (no base ref specified)`
|
|
1088
|
+
);
|
|
1089
|
+
await this.git.raw(["worktree", "add", "-b", branchName, worktreePath]);
|
|
1090
|
+
}
|
|
970
1091
|
}
|
|
971
1092
|
const info = {
|
|
972
1093
|
workerId,
|
|
@@ -976,6 +1097,7 @@ var WorktreeManager = class {
|
|
|
976
1097
|
createdAt: /* @__PURE__ */ new Date()
|
|
977
1098
|
};
|
|
978
1099
|
this.activeWorktrees.set(workerId, info);
|
|
1100
|
+
this.worktreesByName.set(worktreeName, info);
|
|
979
1101
|
logger.info(`Worktree "${worktreeName}" ready at ${worktreePath} on branch ${branchName}`);
|
|
980
1102
|
return info;
|
|
981
1103
|
} catch (error) {
|
|
@@ -1006,30 +1128,42 @@ var WorktreeManager = class {
|
|
|
1006
1128
|
return;
|
|
1007
1129
|
}
|
|
1008
1130
|
try {
|
|
1009
|
-
const
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
);
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
logger.
|
|
1025
|
-
|
|
1131
|
+
const otherWorkersUsingWorktree = Array.from(this.activeWorktrees.values()).filter(
|
|
1132
|
+
(wt) => wt.worktreeName === info.worktreeName && wt.workerId !== workerId
|
|
1133
|
+
);
|
|
1134
|
+
const shouldRemoveWorktree = otherWorkersUsingWorktree.length === 0;
|
|
1135
|
+
if (shouldRemoveWorktree) {
|
|
1136
|
+
const args = ["worktree", "remove", info.worktreePath];
|
|
1137
|
+
if (force) {
|
|
1138
|
+
args.push("--force");
|
|
1139
|
+
}
|
|
1140
|
+
try {
|
|
1141
|
+
await this.git.raw(args);
|
|
1142
|
+
logger.info(`Removed worktree "${info.worktreeName}" at ${info.worktreePath}`);
|
|
1143
|
+
} catch (worktreeError) {
|
|
1144
|
+
const errorMessage = worktreeError instanceof Error ? worktreeError.message : String(worktreeError);
|
|
1145
|
+
if (errorMessage.includes("is not a working tree") || errorMessage.includes("does not exist")) {
|
|
1146
|
+
logger.warn(
|
|
1147
|
+
`Worktree ${info.worktreePath} doesn't exist, may have been manually removed. Cleaning up...`
|
|
1148
|
+
);
|
|
1149
|
+
await this.git.raw(["worktree", "prune"]);
|
|
1150
|
+
try {
|
|
1151
|
+
await fs4.rm(info.worktreePath, { recursive: true, force: true });
|
|
1152
|
+
logger.info(`Manually removed stale worktree directory: ${info.worktreePath}`);
|
|
1153
|
+
} catch {
|
|
1154
|
+
}
|
|
1155
|
+
} else {
|
|
1156
|
+
throw worktreeError;
|
|
1026
1157
|
}
|
|
1027
|
-
} else {
|
|
1028
|
-
throw worktreeError;
|
|
1029
1158
|
}
|
|
1159
|
+
this.worktreesByName.delete(info.worktreeName);
|
|
1160
|
+
} else {
|
|
1161
|
+
logger.info(
|
|
1162
|
+
`Not removing worktree "${info.worktreeName}" - still in use by ${otherWorkersUsingWorktree.length} other worker(s)`
|
|
1163
|
+
);
|
|
1030
1164
|
}
|
|
1031
1165
|
this.activeWorktrees.delete(workerId);
|
|
1032
|
-
logger.info(`Removed worktree for worker ${workerId}`);
|
|
1166
|
+
logger.info(`Removed worktree reference for worker ${workerId}`);
|
|
1033
1167
|
} catch (error) {
|
|
1034
1168
|
logger.error(`Failed to remove worktree for worker ${workerId}:`, error);
|
|
1035
1169
|
throw new WorktreeError(
|
|
@@ -1108,41 +1242,6 @@ var WorktreeManager = class {
|
|
|
1108
1242
|
}
|
|
1109
1243
|
};
|
|
1110
1244
|
|
|
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
|
-
|
|
1146
1245
|
// src/orchestrator/worker-manager.ts
|
|
1147
1246
|
var WorkerManager = class extends EventEmitter {
|
|
1148
1247
|
constructor(options) {
|
|
@@ -1150,37 +1249,16 @@ var WorkerManager = class extends EventEmitter {
|
|
|
1150
1249
|
this.options = options;
|
|
1151
1250
|
this.worktreeManager = new WorktreeManager({
|
|
1152
1251
|
repoPath: options.repoPath,
|
|
1153
|
-
enabled:
|
|
1252
|
+
enabled: options.worktreesEnabled,
|
|
1253
|
+
base_ref: options.worktreesBaseRef
|
|
1154
1254
|
});
|
|
1155
|
-
|
|
1255
|
+
logger.info(
|
|
1256
|
+
`WorkerManager initialized with worktrees ${options.worktreesEnabled ? "enabled" : "disabled"}`
|
|
1257
|
+
);
|
|
1156
1258
|
}
|
|
1157
1259
|
workers = /* @__PURE__ */ new Map();
|
|
1158
1260
|
runningQueries = /* @__PURE__ */ new Map();
|
|
1159
1261
|
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
|
-
}
|
|
1184
1262
|
listWorkers() {
|
|
1185
1263
|
return Array.from(this.workers.values());
|
|
1186
1264
|
}
|
|
@@ -1333,9 +1411,9 @@ var WorkerManager = class extends EventEmitter {
|
|
|
1333
1411
|
success: false,
|
|
1334
1412
|
text: "Worker canceled by user request."
|
|
1335
1413
|
});
|
|
1336
|
-
const
|
|
1337
|
-
if (
|
|
1338
|
-
void
|
|
1414
|
+
const query3 = this.runningQueries.get(id);
|
|
1415
|
+
if (query3) {
|
|
1416
|
+
void query3.interrupt().catch(() => {
|
|
1339
1417
|
});
|
|
1340
1418
|
}
|
|
1341
1419
|
record.messageQueue.close();
|
|
@@ -1385,10 +1463,10 @@ var WorkerManager = class extends EventEmitter {
|
|
|
1385
1463
|
});
|
|
1386
1464
|
record.lastUpdateAt = Date.now();
|
|
1387
1465
|
if (interrupt) {
|
|
1388
|
-
const
|
|
1389
|
-
if (
|
|
1466
|
+
const query3 = this.runningQueries.get(record.id);
|
|
1467
|
+
if (query3) {
|
|
1390
1468
|
try {
|
|
1391
|
-
await
|
|
1469
|
+
await query3.interrupt();
|
|
1392
1470
|
} catch (error) {
|
|
1393
1471
|
console.error("[WorkerManager] Failed to interrupt worker:", error);
|
|
1394
1472
|
}
|
|
@@ -1502,7 +1580,7 @@ var WorkerManager = class extends EventEmitter {
|
|
|
1502
1580
|
record.sessionId,
|
|
1503
1581
|
record.messageQueue
|
|
1504
1582
|
);
|
|
1505
|
-
const
|
|
1583
|
+
const query3 = client.createQuery(messageStream, {
|
|
1506
1584
|
hooks: {
|
|
1507
1585
|
PostToolUse: [
|
|
1508
1586
|
{
|
|
@@ -1543,13 +1621,37 @@ var WorkerManager = class extends EventEmitter {
|
|
|
1543
1621
|
]
|
|
1544
1622
|
}
|
|
1545
1623
|
});
|
|
1546
|
-
this.runningQueries.set(record.id,
|
|
1624
|
+
this.runningQueries.set(record.id, query3);
|
|
1547
1625
|
const summarizedChunks = [];
|
|
1548
1626
|
try {
|
|
1549
|
-
for await (const message of
|
|
1627
|
+
for await (const message of query3) {
|
|
1550
1628
|
this.handleMessage(record, message, summarizedChunks);
|
|
1551
1629
|
}
|
|
1552
1630
|
if (record.status === "running") {
|
|
1631
|
+
if (record.worktreePath) {
|
|
1632
|
+
try {
|
|
1633
|
+
logger.info(
|
|
1634
|
+
`Auto-committing changes for worker ${record.id} in worktree ${record.worktreePath}`
|
|
1635
|
+
);
|
|
1636
|
+
const worktreeGit = simpleGit2(record.worktreePath);
|
|
1637
|
+
await worktreeGit.add("-A");
|
|
1638
|
+
const commitMessage = `Worker task: ${record.task}
|
|
1639
|
+
|
|
1640
|
+
Automatically committed changes from worker completion.`;
|
|
1641
|
+
await worktreeGit.commit(commitMessage);
|
|
1642
|
+
logger.info(`Successfully auto-committed changes for worker ${record.id}`);
|
|
1643
|
+
record.progress.push({
|
|
1644
|
+
timestamp: Date.now(),
|
|
1645
|
+
message: "Auto-committed all changes to git"
|
|
1646
|
+
});
|
|
1647
|
+
} catch (commitError) {
|
|
1648
|
+
logger.error(`Failed to auto-commit changes for worker ${record.id}:`, commitError);
|
|
1649
|
+
record.progress.push({
|
|
1650
|
+
timestamp: Date.now(),
|
|
1651
|
+
message: `Failed to auto-commit changes: ${commitError instanceof Error ? commitError.message : String(commitError)}`
|
|
1652
|
+
});
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1553
1655
|
record.status = "completed";
|
|
1554
1656
|
record.result = summarizedChunks.join("\n").trim() || "Worker completed without additional details.";
|
|
1555
1657
|
record.lastUpdateAt = Date.now();
|
|
@@ -1731,12 +1833,7 @@ WORKTREE ISOLATION:
|
|
|
1731
1833
|
- All your file operations, git commands, and changes are isolated to this worktree.
|
|
1732
1834
|
- Other workers cannot see your changes and you cannot see theirs.
|
|
1733
1835
|
- 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.
|
|
1836
|
+
- Your changes will be automatically committed when you complete your task. Focus on making your changes and reporting your results.
|
|
1740
1837
|
`;
|
|
1741
1838
|
}
|
|
1742
1839
|
return `${userPrompt.trim()}
|
|
@@ -1766,7 +1863,7 @@ ${directive}`;
|
|
|
1766
1863
|
|
|
1767
1864
|
// src/orchestrator/tools/worker-tools.ts
|
|
1768
1865
|
import { createSdkMcpServer as createSdkMcpServer2, tool as tool2 } from "@anthropic-ai/claude-agent-sdk";
|
|
1769
|
-
import { z as
|
|
1866
|
+
import { z as z2 } from "zod";
|
|
1770
1867
|
function createWorkerMcpServer(workerManager, context) {
|
|
1771
1868
|
return createSdkMcpServer2({
|
|
1772
1869
|
name: "worker",
|
|
@@ -1796,14 +1893,14 @@ Usage notes:
|
|
|
1796
1893
|
- The worker's result is returned to you, not the user - you must summarize or forward relevant information
|
|
1797
1894
|
- Worker progress status is automatically included in your context - you can see their current state without additional tools`,
|
|
1798
1895
|
{
|
|
1799
|
-
task:
|
|
1896
|
+
task: z2.string().min(1, "task name is required").describe(
|
|
1800
1897
|
'A short, unique name for this task (e.g., "FizzBuzz Python", "Search codebase", "Fix bug #123"). Used to identify and track this worker.'
|
|
1801
1898
|
),
|
|
1802
|
-
prompt:
|
|
1803
|
-
worktree_name:
|
|
1899
|
+
prompt: z2.string().min(1, "prompt is required"),
|
|
1900
|
+
worktree_name: z2.string().min(1, "worktree_name is required").describe(
|
|
1804
1901
|
"A short descriptive name for a worktree to create that can be used by workers simultaneously. This is synonymous with branch."
|
|
1805
1902
|
),
|
|
1806
|
-
metadata:
|
|
1903
|
+
metadata: z2.record(z2.any()).optional()
|
|
1807
1904
|
},
|
|
1808
1905
|
async (args, extra) => {
|
|
1809
1906
|
const typedExtra = extra;
|
|
@@ -1901,11 +1998,11 @@ Orchestrator: [launches worker: "refactor database"]
|
|
|
1901
1998
|
User: "Wait, actually don't touch the database - just refactor the API layer instead"
|
|
1902
1999
|
Orchestrator: [message_worker: task="refactor database", message="User wants to change scope - refactor API layer instead, leave database alone", interrupt=true]`,
|
|
1903
2000
|
{
|
|
1904
|
-
task:
|
|
1905
|
-
message:
|
|
2001
|
+
task: z2.string().min(1, "task name is required").describe("The task name of the worker to message"),
|
|
2002
|
+
message: z2.string().min(1, "message is required").describe(
|
|
1906
2003
|
"The message to send to the worker. Be clear and specific about what has changed or what additional context is being provided."
|
|
1907
2004
|
),
|
|
1908
|
-
interrupt:
|
|
2005
|
+
interrupt: z2.boolean().default(false).describe(
|
|
1909
2006
|
"If true, interrupt current generation immediately; if false (default), queue message for next turn"
|
|
1910
2007
|
)
|
|
1911
2008
|
},
|
|
@@ -1976,11 +2073,234 @@ import { readFileSync } from "fs";
|
|
|
1976
2073
|
var orchestratorPromptUrl = new URL("./orchestrator-prompt.md", import.meta.url);
|
|
1977
2074
|
var ORCHESTRATOR_SYSTEM_PROMPT = readFileSync(orchestratorPromptUrl, "utf-8");
|
|
1978
2075
|
|
|
2076
|
+
// src/orchestrator/agent-orchestrator.ts
|
|
2077
|
+
import "@anthropic-ai/claude-agent-sdk";
|
|
2078
|
+
|
|
2079
|
+
// src/orchestrator/utils/completions-manager.ts
|
|
2080
|
+
import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
|
|
2081
|
+
import { glob } from "glob";
|
|
2082
|
+
import * as path5 from "path";
|
|
2083
|
+
var CompletionsManager = class {
|
|
2084
|
+
slashCommandsCache;
|
|
2085
|
+
filesCache = /* @__PURE__ */ new Map();
|
|
2086
|
+
async fetchSlashCommands() {
|
|
2087
|
+
if (this.slashCommandsCache && Date.now() - this.slashCommandsCache.fetched.getTime() < 5 * 60 * 1e3) {
|
|
2088
|
+
return this.slashCommandsCache.slashCommands;
|
|
2089
|
+
}
|
|
2090
|
+
let commands = [];
|
|
2091
|
+
for await (const message of query2({
|
|
2092
|
+
prompt: "Respond with one word.",
|
|
2093
|
+
options: { maxTurns: 1, model: "haiku", settingSources: ["user", "project", "local"] }
|
|
2094
|
+
})) {
|
|
2095
|
+
if (message.type === "system" && message.subtype === "init") {
|
|
2096
|
+
console.log("Fetched available slash commands:", message.slash_commands);
|
|
2097
|
+
commands = message.slash_commands;
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
this.slashCommandsCache = {
|
|
2101
|
+
fetched: /* @__PURE__ */ new Date(),
|
|
2102
|
+
slashCommands: commands
|
|
2103
|
+
};
|
|
2104
|
+
return commands;
|
|
2105
|
+
}
|
|
2106
|
+
async fetchFiles(searchPattern = "") {
|
|
2107
|
+
const cached = this.filesCache.get(searchPattern);
|
|
2108
|
+
if (cached && Date.now() - cached.fetched.getTime() < 30 * 1e3) {
|
|
2109
|
+
return cached.files;
|
|
2110
|
+
}
|
|
2111
|
+
try {
|
|
2112
|
+
let patterns = [];
|
|
2113
|
+
if (searchPattern === "") {
|
|
2114
|
+
patterns = [
|
|
2115
|
+
"package.json",
|
|
2116
|
+
"tsconfig.json",
|
|
2117
|
+
"pyproject.toml",
|
|
2118
|
+
"Cargo.toml",
|
|
2119
|
+
"README.md",
|
|
2120
|
+
"CLAUDE.md",
|
|
2121
|
+
"src/index.*",
|
|
2122
|
+
"src/main.*",
|
|
2123
|
+
"src/app.*",
|
|
2124
|
+
"index.*",
|
|
2125
|
+
"main.*",
|
|
2126
|
+
"app.*",
|
|
2127
|
+
"*.config.*",
|
|
2128
|
+
"src/**/*.tsx",
|
|
2129
|
+
"src/**/*.ts",
|
|
2130
|
+
"*.py"
|
|
2131
|
+
];
|
|
2132
|
+
} else {
|
|
2133
|
+
if (searchPattern.includes("/")) {
|
|
2134
|
+
patterns.push(
|
|
2135
|
+
`**/${searchPattern}*`,
|
|
2136
|
+
// Match in any directory
|
|
2137
|
+
`**/${searchPattern}*/**`,
|
|
2138
|
+
// Match directories
|
|
2139
|
+
`${searchPattern}*`,
|
|
2140
|
+
// Match from root
|
|
2141
|
+
`${searchPattern}*/**`
|
|
2142
|
+
// Match root directories
|
|
2143
|
+
);
|
|
2144
|
+
} else {
|
|
2145
|
+
patterns.push(
|
|
2146
|
+
`**/*${searchPattern}*`,
|
|
2147
|
+
// Files containing the pattern
|
|
2148
|
+
`**/*${searchPattern}*.*`,
|
|
2149
|
+
// Files with extensions containing pattern
|
|
2150
|
+
`**/${searchPattern}*`,
|
|
2151
|
+
// Files starting with the pattern
|
|
2152
|
+
`${searchPattern}*`
|
|
2153
|
+
// Files in current directory
|
|
2154
|
+
);
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
const allFiles = /* @__PURE__ */ new Set();
|
|
2158
|
+
for (const pattern of patterns) {
|
|
2159
|
+
try {
|
|
2160
|
+
const files = await glob(pattern, {
|
|
2161
|
+
ignore: [
|
|
2162
|
+
"**/node_modules/**",
|
|
2163
|
+
"**/.git/**",
|
|
2164
|
+
"**/dist/**",
|
|
2165
|
+
"**/build/**",
|
|
2166
|
+
"**/.venv/**",
|
|
2167
|
+
"**/target/**",
|
|
2168
|
+
"**/__pycache__/**",
|
|
2169
|
+
"**/.pytest_cache/**",
|
|
2170
|
+
"**/.next/**",
|
|
2171
|
+
"**/.nuxt/**",
|
|
2172
|
+
"**/coverage/**",
|
|
2173
|
+
"**/*.min.js",
|
|
2174
|
+
"**/*.map",
|
|
2175
|
+
"**/vendor/**",
|
|
2176
|
+
"**/.cache/**"
|
|
2177
|
+
],
|
|
2178
|
+
maxDepth: 10,
|
|
2179
|
+
absolute: false,
|
|
2180
|
+
nodir: true
|
|
2181
|
+
// Only files, not directories
|
|
2182
|
+
});
|
|
2183
|
+
files.forEach((f) => allFiles.add(f));
|
|
2184
|
+
} catch {
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
const fileList = Array.from(allFiles).sort((a, b) => {
|
|
2188
|
+
if (searchPattern === "") {
|
|
2189
|
+
const importantFiles = [
|
|
2190
|
+
"package.json",
|
|
2191
|
+
"tsconfig.json",
|
|
2192
|
+
"pyproject.toml",
|
|
2193
|
+
"README.md",
|
|
2194
|
+
"CLAUDE.md"
|
|
2195
|
+
];
|
|
2196
|
+
const aName2 = path5.basename(a);
|
|
2197
|
+
const bName2 = path5.basename(b);
|
|
2198
|
+
const aImportant = importantFiles.includes(aName2);
|
|
2199
|
+
const bImportant = importantFiles.includes(bName2);
|
|
2200
|
+
if (aImportant && !bImportant) return -1;
|
|
2201
|
+
if (!aImportant && bImportant) return 1;
|
|
2202
|
+
const aDepth2 = (a.match(/\//g) || []).length;
|
|
2203
|
+
const bDepth2 = (b.match(/\//g) || []).length;
|
|
2204
|
+
if (aDepth2 !== bDepth2) return aDepth2 - bDepth2;
|
|
2205
|
+
return a.localeCompare(b);
|
|
2206
|
+
}
|
|
2207
|
+
const aName = path5.basename(a).toLowerCase();
|
|
2208
|
+
const bName = path5.basename(b).toLowerCase();
|
|
2209
|
+
const searchLower = searchPattern.toLowerCase();
|
|
2210
|
+
const aExact = aName === searchLower;
|
|
2211
|
+
const bExact = bName === searchLower;
|
|
2212
|
+
if (aExact && !bExact) return -1;
|
|
2213
|
+
if (!aExact && bExact) return 1;
|
|
2214
|
+
const aStarts = aName.startsWith(searchLower);
|
|
2215
|
+
const bStarts = bName.startsWith(searchLower);
|
|
2216
|
+
if (aStarts && !bStarts) return -1;
|
|
2217
|
+
if (!aStarts && bStarts) return 1;
|
|
2218
|
+
const aDepth = (a.match(/\//g) || []).length;
|
|
2219
|
+
const bDepth = (b.match(/\//g) || []).length;
|
|
2220
|
+
if (aDepth !== bDepth) return aDepth - bDepth;
|
|
2221
|
+
return a.localeCompare(b);
|
|
2222
|
+
}).slice(0, searchPattern === "" ? 20 : 50);
|
|
2223
|
+
this.filesCache.set(searchPattern, {
|
|
2224
|
+
fetched: /* @__PURE__ */ new Date(),
|
|
2225
|
+
files: fileList
|
|
2226
|
+
});
|
|
2227
|
+
if (this.filesCache.size > 100) {
|
|
2228
|
+
const entries = Array.from(this.filesCache.entries());
|
|
2229
|
+
entries.sort((a, b) => a[1].fetched.getTime() - b[1].fetched.getTime()).slice(0, 50).forEach(([key]) => this.filesCache.delete(key));
|
|
2230
|
+
}
|
|
2231
|
+
return fileList;
|
|
2232
|
+
} catch (error) {
|
|
2233
|
+
console.error("Error fetching files:", error);
|
|
2234
|
+
return [];
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
async getCompletion(query3, limit = 50) {
|
|
2238
|
+
const completions = [];
|
|
2239
|
+
if (query3.startsWith("/")) {
|
|
2240
|
+
const slashQuery = query3.slice(1);
|
|
2241
|
+
const commands = (await this.fetchSlashCommands()).filter((cmd) => cmd.startsWith(slashQuery)).slice(0, limit);
|
|
2242
|
+
for (const cmd of commands) {
|
|
2243
|
+
completions.push({
|
|
2244
|
+
type: "slash_command",
|
|
2245
|
+
result: cmd
|
|
2246
|
+
});
|
|
2247
|
+
}
|
|
2248
|
+
} else if (query3.startsWith("@")) {
|
|
2249
|
+
const fileQuery = query3.slice(1);
|
|
2250
|
+
const files = await this.fetchFiles(fileQuery);
|
|
2251
|
+
for (const file of files.slice(0, limit)) {
|
|
2252
|
+
completions.push({
|
|
2253
|
+
type: "file",
|
|
2254
|
+
result: file
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
return completions;
|
|
2259
|
+
}
|
|
2260
|
+
};
|
|
2261
|
+
|
|
2262
|
+
// src/config/omnara-config.ts
|
|
2263
|
+
import { parse as parseJsonc } from "jsonc-parser";
|
|
2264
|
+
import * as fs5 from "fs/promises";
|
|
2265
|
+
import * as path6 from "path";
|
|
2266
|
+
import * as z3 from "zod";
|
|
2267
|
+
var OmnaraConfig = z3.object({
|
|
2268
|
+
worktrees: z3.object({
|
|
2269
|
+
enabled: z3.boolean().default(false),
|
|
2270
|
+
base_ref: z3.string().optional()
|
|
2271
|
+
}).default({
|
|
2272
|
+
enabled: false
|
|
2273
|
+
})
|
|
2274
|
+
});
|
|
2275
|
+
async function readOmnaraConfig(configPath) {
|
|
2276
|
+
const resolveConfig = async (path8) => {
|
|
2277
|
+
if (!path8) {
|
|
2278
|
+
return null;
|
|
2279
|
+
}
|
|
2280
|
+
await fs5.access(path8);
|
|
2281
|
+
const content = await fs5.readFile(path8, "utf-8");
|
|
2282
|
+
const errors = [];
|
|
2283
|
+
const config = parseJsonc(content, errors, {
|
|
2284
|
+
allowTrailingComma: true,
|
|
2285
|
+
allowEmptyContent: true
|
|
2286
|
+
});
|
|
2287
|
+
return OmnaraConfig.parse(config);
|
|
2288
|
+
};
|
|
2289
|
+
const resolveDefault = () => {
|
|
2290
|
+
return OmnaraConfig.parse({});
|
|
2291
|
+
};
|
|
2292
|
+
return await resolveConfig(configPath).catch(() => null) ?? await resolveConfig(path6.join(process.cwd(), "omnara.jsonc")).catch(() => null) ?? await resolveConfig(path6.join(process.cwd(), "omnara.json")).catch(() => null) ?? await resolveDefault();
|
|
2293
|
+
}
|
|
2294
|
+
function isWorktreesEnabled(config) {
|
|
2295
|
+
return config.worktrees.enabled;
|
|
2296
|
+
}
|
|
2297
|
+
|
|
1979
2298
|
// src/orchestrator/agent-orchestrator.ts
|
|
1980
2299
|
var WORKER_LAUNCH_TOOL = "mcp__worker__launch_worker";
|
|
1981
2300
|
var WORKER_GET_TOOL = "mcp__worker__get_workers";
|
|
1982
2301
|
var WORKER_MESSAGE_TOOL = "mcp__worker__message_worker";
|
|
1983
2302
|
var AgentOrchestrator = class {
|
|
2303
|
+
completionsManager;
|
|
1984
2304
|
aiClient;
|
|
1985
2305
|
workerManager;
|
|
1986
2306
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -2004,7 +2324,9 @@ var AgentOrchestrator = class {
|
|
|
2004
2324
|
this.workerManager = dependencies?.workerManager ?? new WorkerManager({
|
|
2005
2325
|
createClient: workerClientFactory,
|
|
2006
2326
|
resolveSessionIdBySdk,
|
|
2007
|
-
repoPath: dependencies?.repoPath ?? process.cwd()
|
|
2327
|
+
repoPath: dependencies?.repoPath ?? process.cwd(),
|
|
2328
|
+
worktreesEnabled: dependencies?.worktreesEnabled ?? false,
|
|
2329
|
+
worktreesBaseRef: dependencies?.worktreesBaseRef
|
|
2008
2330
|
});
|
|
2009
2331
|
const workerMcpServer = createWorkerMcpServer(this.workerManager, {
|
|
2010
2332
|
resolveSessionIdBySdk,
|
|
@@ -2028,6 +2350,7 @@ var AgentOrchestrator = class {
|
|
|
2028
2350
|
}));
|
|
2029
2351
|
this.aiClient = mainClientFactory();
|
|
2030
2352
|
this.registerWorkerListeners();
|
|
2353
|
+
this.completionsManager = new CompletionsManager();
|
|
2031
2354
|
}
|
|
2032
2355
|
ensureSessionExists(sessionId) {
|
|
2033
2356
|
const existing = this.sessions.get(sessionId);
|
|
@@ -2109,6 +2432,14 @@ var AgentOrchestrator = class {
|
|
|
2109
2432
|
this.sessions.clear();
|
|
2110
2433
|
this.started = false;
|
|
2111
2434
|
}
|
|
2435
|
+
async cancel() {
|
|
2436
|
+
await Promise.all(
|
|
2437
|
+
this.getWorkerManager().listWorkers().filter((w) => w.status === "running").map((worker) => {
|
|
2438
|
+
return this.getWorkerManager().cancelWorker(worker.id);
|
|
2439
|
+
})
|
|
2440
|
+
);
|
|
2441
|
+
await this.aiClient.cancel();
|
|
2442
|
+
}
|
|
2112
2443
|
handleDisconnect(connection) {
|
|
2113
2444
|
for (const sessionId of connection.sessionIds) {
|
|
2114
2445
|
const session = this.sessions.get(sessionId);
|
|
@@ -2528,10 +2859,17 @@ var AgentOrchestrator = class {
|
|
|
2528
2859
|
buildWorkerStatusMessage(triggerWorkerId) {
|
|
2529
2860
|
const allWorkers = this.workerManager.listWorkers();
|
|
2530
2861
|
if (allWorkers.length === 0) {
|
|
2531
|
-
return "[WORKER STATUS UPDATE]\n\nNo active workers
|
|
2532
|
-
}
|
|
2533
|
-
const lines = [
|
|
2534
|
-
|
|
2862
|
+
return "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n[AUTOMATED SYSTEM NOTIFICATION: WORKER STATUS UPDATE]\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nNo active workers.\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nEND OF AUTOMATED NOTIFICATION\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550";
|
|
2863
|
+
}
|
|
2864
|
+
const lines = [
|
|
2865
|
+
"\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",
|
|
2866
|
+
"[AUTOMATED SYSTEM NOTIFICATION: WORKER STATUS UPDATE]",
|
|
2867
|
+
"\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",
|
|
2868
|
+
"",
|
|
2869
|
+
"This is an automated notification triggered by a worker completion.",
|
|
2870
|
+
"You should provide an INCREMENTAL summary of only the NEW completion to the user.",
|
|
2871
|
+
""
|
|
2872
|
+
];
|
|
2535
2873
|
for (const worker of allWorkers) {
|
|
2536
2874
|
const isTrigger = worker.id === triggerWorkerId;
|
|
2537
2875
|
const prefix = isTrigger ? ">>> " : " ";
|
|
@@ -2565,6 +2903,9 @@ var AgentOrchestrator = class {
|
|
|
2565
2903
|
}
|
|
2566
2904
|
lines.push("");
|
|
2567
2905
|
}
|
|
2906
|
+
lines.push("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
2907
|
+
lines.push("END OF AUTOMATED NOTIFICATION");
|
|
2908
|
+
lines.push("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
2568
2909
|
return lines.join("\n");
|
|
2569
2910
|
}
|
|
2570
2911
|
broadcastToAll(message) {
|
|
@@ -2594,7 +2935,41 @@ var AgentOrchestrator = class {
|
|
|
2594
2935
|
this.handleDisconnect(connection);
|
|
2595
2936
|
}
|
|
2596
2937
|
}
|
|
2938
|
+
async getCompletions(request) {
|
|
2939
|
+
return {
|
|
2940
|
+
type: "completions",
|
|
2941
|
+
completions: await this.completionsManager.getCompletion(request.query)
|
|
2942
|
+
};
|
|
2943
|
+
}
|
|
2597
2944
|
};
|
|
2945
|
+
async function startAgentOrchestrator(dependencies) {
|
|
2946
|
+
let worktreesEnabled = dependencies?.worktreesEnabled ?? false;
|
|
2947
|
+
let worktreesBaseRef = dependencies?.worktreesBaseRef;
|
|
2948
|
+
if (dependencies?.worktreesEnabled === void 0) {
|
|
2949
|
+
try {
|
|
2950
|
+
const config = await readOmnaraConfig();
|
|
2951
|
+
worktreesEnabled = isWorktreesEnabled(config);
|
|
2952
|
+
worktreesBaseRef = config.worktrees.base_ref;
|
|
2953
|
+
if (worktreesEnabled) {
|
|
2954
|
+
logger.info("Worktrees enabled via omnara.jsonc configuration");
|
|
2955
|
+
} else {
|
|
2956
|
+
logger.info("Worktrees disabled (no worktrees config found in omnara.jsonc)");
|
|
2957
|
+
}
|
|
2958
|
+
} catch (err) {
|
|
2959
|
+
logger.error("Failed to read omnara.jsonc configuration:", err);
|
|
2960
|
+
logger.info("Worktrees disabled due to config read error");
|
|
2961
|
+
worktreesEnabled = false;
|
|
2962
|
+
worktreesBaseRef = void 0;
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
const server = new AgentOrchestrator({
|
|
2966
|
+
...dependencies,
|
|
2967
|
+
worktreesEnabled,
|
|
2968
|
+
worktreesBaseRef
|
|
2969
|
+
});
|
|
2970
|
+
await server.start();
|
|
2971
|
+
return server;
|
|
2972
|
+
}
|
|
2598
2973
|
|
|
2599
2974
|
// src/relay/relay-bridge.ts
|
|
2600
2975
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
@@ -2616,8 +2991,8 @@ var ApiClient = class {
|
|
|
2616
2991
|
constructor(options) {
|
|
2617
2992
|
this.baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
2618
2993
|
}
|
|
2619
|
-
async getJson(
|
|
2620
|
-
const url = resolveUrl(this.baseUrl,
|
|
2994
|
+
async getJson(path8, options) {
|
|
2995
|
+
const url = resolveUrl(this.baseUrl, path8);
|
|
2621
2996
|
const headers = this.buildHeaders(options);
|
|
2622
2997
|
const response = await fetch(url, {
|
|
2623
2998
|
method: "GET",
|
|
@@ -2626,8 +3001,8 @@ var ApiClient = class {
|
|
|
2626
3001
|
});
|
|
2627
3002
|
return await this.handleResponse(response);
|
|
2628
3003
|
}
|
|
2629
|
-
async postJson(
|
|
2630
|
-
const url = resolveUrl(this.baseUrl,
|
|
3004
|
+
async postJson(path8, body, options) {
|
|
3005
|
+
const url = resolveUrl(this.baseUrl, path8);
|
|
2631
3006
|
const headers = this.buildHeaders(options, { includeJson: true });
|
|
2632
3007
|
const response = await fetch(url, {
|
|
2633
3008
|
method: "POST",
|
|
@@ -2685,12 +3060,12 @@ function normalizeBaseUrl(raw) {
|
|
|
2685
3060
|
}
|
|
2686
3061
|
return raw.endsWith("/") ? raw : `${raw}/`;
|
|
2687
3062
|
}
|
|
2688
|
-
function resolveUrl(base,
|
|
3063
|
+
function resolveUrl(base, path8) {
|
|
2689
3064
|
const baseUrl = new URL(base);
|
|
2690
|
-
if (!
|
|
2691
|
-
|
|
3065
|
+
if (!path8.startsWith("/")) {
|
|
3066
|
+
path8 = `/${path8}`;
|
|
2692
3067
|
}
|
|
2693
|
-
const resolved = new URL(
|
|
3068
|
+
const resolved = new URL(path8, baseUrl);
|
|
2694
3069
|
return resolved.toString();
|
|
2695
3070
|
}
|
|
2696
3071
|
function extractDetail(payload) {
|
|
@@ -3433,6 +3808,166 @@ var control_message_schema_default = {
|
|
|
3433
3808
|
title: "AgentWorkStatus",
|
|
3434
3809
|
type: "string"
|
|
3435
3810
|
},
|
|
3811
|
+
CancelAgentMessage: {
|
|
3812
|
+
additionalProperties: false,
|
|
3813
|
+
description: "Request to cancel a running agent generation.",
|
|
3814
|
+
properties: {
|
|
3815
|
+
type: {
|
|
3816
|
+
const: "cancel_agent",
|
|
3817
|
+
default: "cancel_agent",
|
|
3818
|
+
title: "Type",
|
|
3819
|
+
type: "string"
|
|
3820
|
+
},
|
|
3821
|
+
target_session_id: {
|
|
3822
|
+
format: "uuid",
|
|
3823
|
+
title: "Target Session Id",
|
|
3824
|
+
type: "string"
|
|
3825
|
+
},
|
|
3826
|
+
reason: {
|
|
3827
|
+
anyOf: [
|
|
3828
|
+
{
|
|
3829
|
+
type: "string"
|
|
3830
|
+
},
|
|
3831
|
+
{
|
|
3832
|
+
type: "null"
|
|
3833
|
+
}
|
|
3834
|
+
],
|
|
3835
|
+
default: null,
|
|
3836
|
+
title: "Reason"
|
|
3837
|
+
}
|
|
3838
|
+
},
|
|
3839
|
+
required: [
|
|
3840
|
+
"target_session_id"
|
|
3841
|
+
],
|
|
3842
|
+
title: "CancelAgentMessage",
|
|
3843
|
+
type: "object"
|
|
3844
|
+
},
|
|
3845
|
+
CompletionItem: {
|
|
3846
|
+
description: "A single completion item with type and result",
|
|
3847
|
+
properties: {
|
|
3848
|
+
type: {
|
|
3849
|
+
enum: [
|
|
3850
|
+
"slash_command",
|
|
3851
|
+
"file",
|
|
3852
|
+
"agent"
|
|
3853
|
+
],
|
|
3854
|
+
title: "Type",
|
|
3855
|
+
type: "string"
|
|
3856
|
+
},
|
|
3857
|
+
result: {
|
|
3858
|
+
title: "Result",
|
|
3859
|
+
type: "string"
|
|
3860
|
+
}
|
|
3861
|
+
},
|
|
3862
|
+
required: [
|
|
3863
|
+
"type",
|
|
3864
|
+
"result"
|
|
3865
|
+
],
|
|
3866
|
+
title: "CompletionItem",
|
|
3867
|
+
type: "object"
|
|
3868
|
+
},
|
|
3869
|
+
CompletionsRequest: {
|
|
3870
|
+
description: "Query for autocomplete options",
|
|
3871
|
+
properties: {
|
|
3872
|
+
type: {
|
|
3873
|
+
const: "completions",
|
|
3874
|
+
default: "completions",
|
|
3875
|
+
title: "Type",
|
|
3876
|
+
type: "string"
|
|
3877
|
+
},
|
|
3878
|
+
query: {
|
|
3879
|
+
title: "Query",
|
|
3880
|
+
type: "string"
|
|
3881
|
+
}
|
|
3882
|
+
},
|
|
3883
|
+
required: [
|
|
3884
|
+
"query"
|
|
3885
|
+
],
|
|
3886
|
+
title: "CompletionsRequest",
|
|
3887
|
+
type: "object"
|
|
3888
|
+
},
|
|
3889
|
+
CompletionsResponse: {
|
|
3890
|
+
description: "List of autocomplete options (i.e. files or /commands)",
|
|
3891
|
+
properties: {
|
|
3892
|
+
type: {
|
|
3893
|
+
const: "completions",
|
|
3894
|
+
default: "completions",
|
|
3895
|
+
title: "Type",
|
|
3896
|
+
type: "string"
|
|
3897
|
+
},
|
|
3898
|
+
completions: {
|
|
3899
|
+
items: {
|
|
3900
|
+
$ref: "#/$defs/CompletionItem"
|
|
3901
|
+
},
|
|
3902
|
+
title: "Completions",
|
|
3903
|
+
type: "array"
|
|
3904
|
+
}
|
|
3905
|
+
},
|
|
3906
|
+
required: [
|
|
3907
|
+
"completions"
|
|
3908
|
+
],
|
|
3909
|
+
title: "CompletionsResponse",
|
|
3910
|
+
type: "object"
|
|
3911
|
+
},
|
|
3912
|
+
ErrorResponse: {
|
|
3913
|
+
properties: {
|
|
3914
|
+
type: {
|
|
3915
|
+
const: "error",
|
|
3916
|
+
default: "error",
|
|
3917
|
+
title: "Type",
|
|
3918
|
+
type: "string"
|
|
3919
|
+
},
|
|
3920
|
+
error: {
|
|
3921
|
+
title: "Error",
|
|
3922
|
+
type: "string"
|
|
3923
|
+
}
|
|
3924
|
+
},
|
|
3925
|
+
required: [
|
|
3926
|
+
"error"
|
|
3927
|
+
],
|
|
3928
|
+
title: "ErrorResponse",
|
|
3929
|
+
type: "object"
|
|
3930
|
+
},
|
|
3931
|
+
GitDiffRequest: {
|
|
3932
|
+
description: "Query the git diff on a branch",
|
|
3933
|
+
properties: {
|
|
3934
|
+
type: {
|
|
3935
|
+
const: "git_diff",
|
|
3936
|
+
default: "git_diff",
|
|
3937
|
+
title: "Type",
|
|
3938
|
+
type: "string"
|
|
3939
|
+
},
|
|
3940
|
+
branch: {
|
|
3941
|
+
title: "Branch",
|
|
3942
|
+
type: "string"
|
|
3943
|
+
}
|
|
3944
|
+
},
|
|
3945
|
+
required: [
|
|
3946
|
+
"branch"
|
|
3947
|
+
],
|
|
3948
|
+
title: "GitDiffRequest",
|
|
3949
|
+
type: "object"
|
|
3950
|
+
},
|
|
3951
|
+
GitDiffResponse: {
|
|
3952
|
+
description: "Git diff response for a given branch\nTODO: add the actual definition",
|
|
3953
|
+
properties: {
|
|
3954
|
+
type: {
|
|
3955
|
+
const: "git_diff",
|
|
3956
|
+
default: "git_diff",
|
|
3957
|
+
title: "Type",
|
|
3958
|
+
type: "string"
|
|
3959
|
+
},
|
|
3960
|
+
diff: {
|
|
3961
|
+
title: "Diff",
|
|
3962
|
+
type: "null"
|
|
3963
|
+
}
|
|
3964
|
+
},
|
|
3965
|
+
required: [
|
|
3966
|
+
"diff"
|
|
3967
|
+
],
|
|
3968
|
+
title: "GitDiffResponse",
|
|
3969
|
+
type: "object"
|
|
3970
|
+
},
|
|
3436
3971
|
Heartbeat: {
|
|
3437
3972
|
additionalProperties: false,
|
|
3438
3973
|
description: "Ping/pong heartbeat frames.",
|
|
@@ -3469,6 +4004,77 @@ var control_message_schema_default = {
|
|
|
3469
4004
|
title: "Heartbeat",
|
|
3470
4005
|
type: "object"
|
|
3471
4006
|
},
|
|
4007
|
+
RelayRequestMessage: {
|
|
4008
|
+
additionalProperties: false,
|
|
4009
|
+
description: "RPC message for generic requests",
|
|
4010
|
+
properties: {
|
|
4011
|
+
type: {
|
|
4012
|
+
const: "request",
|
|
4013
|
+
default: "request",
|
|
4014
|
+
title: "Type",
|
|
4015
|
+
type: "string"
|
|
4016
|
+
},
|
|
4017
|
+
request_id: {
|
|
4018
|
+
format: "uuid",
|
|
4019
|
+
title: "Request Id",
|
|
4020
|
+
type: "string"
|
|
4021
|
+
},
|
|
4022
|
+
request: {
|
|
4023
|
+
anyOf: [
|
|
4024
|
+
{
|
|
4025
|
+
$ref: "#/$defs/CompletionsRequest"
|
|
4026
|
+
},
|
|
4027
|
+
{
|
|
4028
|
+
$ref: "#/$defs/GitDiffRequest"
|
|
4029
|
+
}
|
|
4030
|
+
],
|
|
4031
|
+
title: "Request"
|
|
4032
|
+
}
|
|
4033
|
+
},
|
|
4034
|
+
required: [
|
|
4035
|
+
"request_id",
|
|
4036
|
+
"request"
|
|
4037
|
+
],
|
|
4038
|
+
title: "RelayRequestMessage",
|
|
4039
|
+
type: "object"
|
|
4040
|
+
},
|
|
4041
|
+
RelayResponseMessage: {
|
|
4042
|
+
additionalProperties: false,
|
|
4043
|
+
description: "RPC message for responses",
|
|
4044
|
+
properties: {
|
|
4045
|
+
type: {
|
|
4046
|
+
const: "response",
|
|
4047
|
+
default: "response",
|
|
4048
|
+
title: "Type",
|
|
4049
|
+
type: "string"
|
|
4050
|
+
},
|
|
4051
|
+
request_id: {
|
|
4052
|
+
format: "uuid",
|
|
4053
|
+
title: "Request Id",
|
|
4054
|
+
type: "string"
|
|
4055
|
+
},
|
|
4056
|
+
response: {
|
|
4057
|
+
anyOf: [
|
|
4058
|
+
{
|
|
4059
|
+
$ref: "#/$defs/ErrorResponse"
|
|
4060
|
+
},
|
|
4061
|
+
{
|
|
4062
|
+
$ref: "#/$defs/CompletionsResponse"
|
|
4063
|
+
},
|
|
4064
|
+
{
|
|
4065
|
+
$ref: "#/$defs/GitDiffResponse"
|
|
4066
|
+
}
|
|
4067
|
+
],
|
|
4068
|
+
title: "Response"
|
|
4069
|
+
}
|
|
4070
|
+
},
|
|
4071
|
+
required: [
|
|
4072
|
+
"request_id",
|
|
4073
|
+
"response"
|
|
4074
|
+
],
|
|
4075
|
+
title: "RelayResponseMessage",
|
|
4076
|
+
type: "object"
|
|
4077
|
+
},
|
|
3472
4078
|
SessionAnnounce: {
|
|
3473
4079
|
additionalProperties: false,
|
|
3474
4080
|
description: "Notification that a session was created or updated.",
|
|
@@ -3769,6 +4375,15 @@ var control_message_schema_default = {
|
|
|
3769
4375
|
},
|
|
3770
4376
|
{
|
|
3771
4377
|
$ref: "#/$defs/Heartbeat"
|
|
4378
|
+
},
|
|
4379
|
+
{
|
|
4380
|
+
$ref: "#/$defs/CancelAgentMessage"
|
|
4381
|
+
},
|
|
4382
|
+
{
|
|
4383
|
+
$ref: "#/$defs/RelayRequestMessage"
|
|
4384
|
+
},
|
|
4385
|
+
{
|
|
4386
|
+
$ref: "#/$defs/RelayResponseMessage"
|
|
3772
4387
|
}
|
|
3773
4388
|
]
|
|
3774
4389
|
};
|
|
@@ -5698,6 +6313,7 @@ var RelayBridge = class extends EventEmitter2 {
|
|
|
5698
6313
|
userSessionId = null;
|
|
5699
6314
|
currentAgentSessionId = null;
|
|
5700
6315
|
browserOpened = false;
|
|
6316
|
+
canceled = false;
|
|
5701
6317
|
constructor(orchestrator, options) {
|
|
5702
6318
|
super();
|
|
5703
6319
|
this.orchestrator = orchestrator;
|
|
@@ -5711,7 +6327,7 @@ var RelayBridge = class extends EventEmitter2 {
|
|
|
5711
6327
|
throw new Error("Relay bridge already started");
|
|
5712
6328
|
}
|
|
5713
6329
|
this.connectionHandle = this.orchestrator.addClient({
|
|
5714
|
-
canSend: () => this.ws?.readyState === WebSocket.OPEN,
|
|
6330
|
+
canSend: () => !this.canceled && this.ws?.readyState === WebSocket.OPEN,
|
|
5715
6331
|
send: (message) => {
|
|
5716
6332
|
this.handleOutbound(message);
|
|
5717
6333
|
}
|
|
@@ -5742,6 +6358,7 @@ var RelayBridge = class extends EventEmitter2 {
|
|
|
5742
6358
|
this.stopped = true;
|
|
5743
6359
|
this.currentAgentSessionId = null;
|
|
5744
6360
|
this.browserOpened = false;
|
|
6361
|
+
this.canceled = false;
|
|
5745
6362
|
if (this.pingTimer) {
|
|
5746
6363
|
clearInterval(this.pingTimer);
|
|
5747
6364
|
this.pingTimer = null;
|
|
@@ -5976,9 +6593,7 @@ var RelayBridge = class extends EventEmitter2 {
|
|
|
5976
6593
|
this.handleSessionAnnounce(control);
|
|
5977
6594
|
break;
|
|
5978
6595
|
case "session_closed":
|
|
5979
|
-
this.
|
|
5980
|
-
`Session closed: ${control.session_id} -> status ${control.status}`
|
|
5981
|
-
);
|
|
6596
|
+
this.handleSessionClosed(control);
|
|
5982
6597
|
break;
|
|
5983
6598
|
case "heartbeat":
|
|
5984
6599
|
if (control.mode === "ping") {
|
|
@@ -5990,6 +6605,12 @@ var RelayBridge = class extends EventEmitter2 {
|
|
|
5990
6605
|
this.sendFrame(response);
|
|
5991
6606
|
}
|
|
5992
6607
|
break;
|
|
6608
|
+
case "cancel_agent":
|
|
6609
|
+
this.handleCancelAgent(control);
|
|
6610
|
+
break;
|
|
6611
|
+
case "request":
|
|
6612
|
+
this.handleRelayRequest(control);
|
|
6613
|
+
break;
|
|
5993
6614
|
default:
|
|
5994
6615
|
this.logger.debug(`Ignoring control message ${type ?? "unknown"}`);
|
|
5995
6616
|
}
|
|
@@ -6056,6 +6677,68 @@ var RelayBridge = class extends EventEmitter2 {
|
|
|
6056
6677
|
this.orchestrator.ensureSessionExists(control.session.session_id);
|
|
6057
6678
|
}
|
|
6058
6679
|
}
|
|
6680
|
+
async handleCancelAgent(control) {
|
|
6681
|
+
this.logger.info(
|
|
6682
|
+
`Received cancel request for session ${control.target_session_id}${control.reason ? ` (reason: ${control.reason})` : ""}`
|
|
6683
|
+
);
|
|
6684
|
+
if (control.target_session_id === this.currentAgentSessionId) {
|
|
6685
|
+
this.logger.info("Cancel request for main orchestrator session");
|
|
6686
|
+
await this.orchestrator.cancel();
|
|
6687
|
+
} else {
|
|
6688
|
+
const success = this.orchestrator.getWorkerManager().cancelWorker(control.target_session_id);
|
|
6689
|
+
if (success) {
|
|
6690
|
+
this.logger.info(`Worker cancellation succeeded for ${control.target_session_id}`);
|
|
6691
|
+
} else {
|
|
6692
|
+
this.logger.warn(
|
|
6693
|
+
`Worker cancellation failed for ${control.target_session_id} (worker not found or already completed)`
|
|
6694
|
+
);
|
|
6695
|
+
}
|
|
6696
|
+
}
|
|
6697
|
+
}
|
|
6698
|
+
handleSessionClosed(control) {
|
|
6699
|
+
const closedSessionId = control.session_id;
|
|
6700
|
+
this.logger.info(`Session closed: ${closedSessionId} -> status ${control.status}`);
|
|
6701
|
+
if (closedSessionId === this.currentAgentSessionId) {
|
|
6702
|
+
this.logger.info(
|
|
6703
|
+
`Main orchestrator session ${closedSessionId} was canceled. Stopping message sending.`
|
|
6704
|
+
);
|
|
6705
|
+
this.canceled = true;
|
|
6706
|
+
}
|
|
6707
|
+
}
|
|
6708
|
+
async handleRelayRequest(control) {
|
|
6709
|
+
const requestId = control.request_id;
|
|
6710
|
+
const request = control.request;
|
|
6711
|
+
let response;
|
|
6712
|
+
switch (request.type) {
|
|
6713
|
+
case "completions":
|
|
6714
|
+
response = await this.orchestrator.getCompletions(
|
|
6715
|
+
request
|
|
6716
|
+
);
|
|
6717
|
+
break;
|
|
6718
|
+
case "git_diff":
|
|
6719
|
+
this.logger.warn("Git diff not implemented");
|
|
6720
|
+
response = {
|
|
6721
|
+
type: "git_diff",
|
|
6722
|
+
diff: null
|
|
6723
|
+
};
|
|
6724
|
+
break;
|
|
6725
|
+
default:
|
|
6726
|
+
response = {
|
|
6727
|
+
type: "error",
|
|
6728
|
+
error: `Request failed: Unknown relay request type ${request.type}`
|
|
6729
|
+
};
|
|
6730
|
+
}
|
|
6731
|
+
const socket = this.ws;
|
|
6732
|
+
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
|
6733
|
+
return;
|
|
6734
|
+
}
|
|
6735
|
+
const frame = {
|
|
6736
|
+
type: "response",
|
|
6737
|
+
request_id: requestId,
|
|
6738
|
+
response
|
|
6739
|
+
};
|
|
6740
|
+
this.sendFrame(frame, socket);
|
|
6741
|
+
}
|
|
6059
6742
|
handleMessageEnvelope(envelope) {
|
|
6060
6743
|
const content = envelope.payload.content;
|
|
6061
6744
|
const isUserMessage = envelope.sender.kind === "user";
|
|
@@ -6312,16 +6995,16 @@ var RelayBridge = class extends EventEmitter2 {
|
|
|
6312
6995
|
}
|
|
6313
6996
|
}
|
|
6314
6997
|
};
|
|
6315
|
-
function buildRelayWebsocketUrl(baseUrl,
|
|
6998
|
+
function buildRelayWebsocketUrl(baseUrl, path8) {
|
|
6316
6999
|
const url = new URL(baseUrl);
|
|
6317
7000
|
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
6318
7001
|
url.search = "";
|
|
6319
7002
|
url.hash = "";
|
|
6320
|
-
if (
|
|
6321
|
-
url.pathname =
|
|
7003
|
+
if (path8.startsWith("/")) {
|
|
7004
|
+
url.pathname = path8;
|
|
6322
7005
|
} else {
|
|
6323
7006
|
const basePath = url.pathname.endsWith("/") ? url.pathname : `${url.pathname}/`;
|
|
6324
|
-
url.pathname = `${basePath}${
|
|
7007
|
+
url.pathname = `${basePath}${path8}`;
|
|
6325
7008
|
}
|
|
6326
7009
|
return url.toString();
|
|
6327
7010
|
}
|
|
@@ -6664,7 +7347,7 @@ Commands:
|
|
|
6664
7347
|
|
|
6665
7348
|
// src/voice/voice-service.ts
|
|
6666
7349
|
async function launchVoiceForSession(client, accessToken, options) {
|
|
6667
|
-
const
|
|
7350
|
+
const path8 = `/api/v1/voice/sessions/${options.sessionId}/launch`;
|
|
6668
7351
|
const requestBody = {};
|
|
6669
7352
|
if (options.force) {
|
|
6670
7353
|
requestBody.force = true;
|
|
@@ -6673,7 +7356,7 @@ async function launchVoiceForSession(client, accessToken, options) {
|
|
|
6673
7356
|
requestBody.room_name = options.roomName;
|
|
6674
7357
|
}
|
|
6675
7358
|
const response = await client.postJson(
|
|
6676
|
-
|
|
7359
|
+
path8,
|
|
6677
7360
|
requestBody,
|
|
6678
7361
|
{ accessToken }
|
|
6679
7362
|
);
|
|
@@ -6769,8 +7452,7 @@ async function runAgentOrchestrator(options = {}) {
|
|
|
6769
7452
|
} else {
|
|
6770
7453
|
logger.info("\u2139 Running without Claude Code integration");
|
|
6771
7454
|
}
|
|
6772
|
-
const orchestrator =
|
|
6773
|
-
await orchestrator.start();
|
|
7455
|
+
const orchestrator = await startAgentOrchestrator();
|
|
6774
7456
|
const backendUrl = resolveBackendUrl(options.backendUrl);
|
|
6775
7457
|
const frontendUrl = resolveFrontendUrl(options.frontendUrl);
|
|
6776
7458
|
const store = new CredentialsStore();
|
|
@@ -6875,16 +7557,16 @@ async function runVoice(options) {
|
|
|
6875
7557
|
logger.debug(launch.relayToken);
|
|
6876
7558
|
logger.important("\nKeep this information secure\u2014tokens grant access to your agent session.");
|
|
6877
7559
|
}
|
|
6878
|
-
function buildRelayWebsocketUrl2(baseUrl,
|
|
7560
|
+
function buildRelayWebsocketUrl2(baseUrl, path8) {
|
|
6879
7561
|
const url = new URL(baseUrl);
|
|
6880
7562
|
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
6881
7563
|
url.search = "";
|
|
6882
7564
|
url.hash = "";
|
|
6883
|
-
if (
|
|
6884
|
-
url.pathname =
|
|
7565
|
+
if (path8.startsWith("/")) {
|
|
7566
|
+
url.pathname = path8;
|
|
6885
7567
|
} else {
|
|
6886
7568
|
const basePath = url.pathname.endsWith("/") ? url.pathname : `${url.pathname}/`;
|
|
6887
|
-
url.pathname = `${basePath}${
|
|
7569
|
+
url.pathname = `${basePath}${path8}`;
|
|
6888
7570
|
}
|
|
6889
7571
|
return url.toString();
|
|
6890
7572
|
}
|
|
@@ -7057,9 +7739,9 @@ function isMainModule() {
|
|
|
7057
7739
|
if (!import.meta.url || !process.argv[1]) {
|
|
7058
7740
|
return false;
|
|
7059
7741
|
}
|
|
7060
|
-
const executedPath =
|
|
7742
|
+
const executedPath = path7.resolve(process.argv[1]);
|
|
7061
7743
|
const currentPath = import.meta.url.startsWith("file://") ? decodeURIComponent(import.meta.url.slice(7)) : import.meta.url;
|
|
7062
|
-
return
|
|
7744
|
+
return path7.normalize(currentPath) === path7.normalize(executedPath);
|
|
7063
7745
|
}
|
|
7064
7746
|
if (isMainModule()) {
|
|
7065
7747
|
run(process.argv).catch((error) => {
|