opencara 0.12.0 → 0.13.0
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/index.js +108 -40
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -137,34 +137,36 @@ var KNOWN_TOOL_NAMES = new Set(DEFAULT_REGISTRY.tools.map((t) => t.name));
|
|
|
137
137
|
var TOOL_ALIASES = {
|
|
138
138
|
"claude-code": "claude"
|
|
139
139
|
};
|
|
140
|
-
function parseRepoConfig(obj, index) {
|
|
141
|
-
const raw = obj
|
|
140
|
+
function parseRepoConfig(obj, index, field = "repos") {
|
|
141
|
+
const raw = obj[field];
|
|
142
142
|
if (raw === void 0 || raw === null) return void 0;
|
|
143
143
|
if (typeof raw !== "object") {
|
|
144
|
-
throw new RepoConfigError(`agents[${index}]
|
|
144
|
+
throw new RepoConfigError(`agents[${index}].${field} must be an object`);
|
|
145
145
|
}
|
|
146
146
|
const reposObj = raw;
|
|
147
147
|
const mode = reposObj.mode;
|
|
148
148
|
if (mode === void 0) {
|
|
149
|
-
throw new RepoConfigError(`agents[${index}].
|
|
149
|
+
throw new RepoConfigError(`agents[${index}].${field}.mode is required`);
|
|
150
150
|
}
|
|
151
151
|
if (typeof mode !== "string" || !VALID_REPO_MODES.includes(mode)) {
|
|
152
152
|
throw new RepoConfigError(
|
|
153
|
-
`agents[${index}].
|
|
153
|
+
`agents[${index}].${field}.mode must be one of: ${VALID_REPO_MODES.join(", ")}`
|
|
154
154
|
);
|
|
155
155
|
}
|
|
156
156
|
const config = { mode };
|
|
157
|
+
const list = reposObj.list;
|
|
157
158
|
if (mode === "whitelist" || mode === "blacklist") {
|
|
158
|
-
const list = reposObj.list;
|
|
159
159
|
if (!Array.isArray(list) || list.length === 0) {
|
|
160
160
|
throw new RepoConfigError(
|
|
161
|
-
`agents[${index}].
|
|
161
|
+
`agents[${index}].${field}.list is required and must be non-empty for mode '${mode}'`
|
|
162
162
|
);
|
|
163
163
|
}
|
|
164
|
+
}
|
|
165
|
+
if (Array.isArray(list) && list.length > 0) {
|
|
164
166
|
for (let j = 0; j < list.length; j++) {
|
|
165
167
|
if (typeof list[j] !== "string" || !REPO_PATTERN.test(list[j])) {
|
|
166
168
|
throw new RepoConfigError(
|
|
167
|
-
`agents[${index}].
|
|
169
|
+
`agents[${index}].${field}.list[${j}] must match 'owner/repo' format`
|
|
168
170
|
);
|
|
169
171
|
}
|
|
170
172
|
}
|
|
@@ -211,10 +213,18 @@ function parseAgents(data) {
|
|
|
211
213
|
if (typeof obj.command === "string") agent.command = obj.command;
|
|
212
214
|
if (obj.router === true) agent.router = true;
|
|
213
215
|
if (obj.review_only === true) agent.review_only = true;
|
|
216
|
+
if (obj.synthesizer_only === true) agent.synthesizer_only = true;
|
|
217
|
+
if (agent.review_only && agent.synthesizer_only) {
|
|
218
|
+
throw new ConfigValidationError(
|
|
219
|
+
`agents[${i}]: review_only and synthesizer_only cannot both be true`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
214
222
|
if (typeof obj.github_token === "string") agent.github_token = obj.github_token;
|
|
215
223
|
if (typeof obj.codebase_dir === "string") agent.codebase_dir = obj.codebase_dir;
|
|
216
224
|
const repoConfig = parseRepoConfig(obj, i);
|
|
217
225
|
if (repoConfig) agent.repos = repoConfig;
|
|
226
|
+
const synthesizeRepoConfig = parseRepoConfig(obj, i, "synthesize_repos");
|
|
227
|
+
if (synthesizeRepoConfig) agent.synthesize_repos = synthesizeRepoConfig;
|
|
218
228
|
agents.push(agent);
|
|
219
229
|
}
|
|
220
230
|
return agents;
|
|
@@ -255,6 +265,7 @@ function loadConfig() {
|
|
|
255
265
|
maxDiffSizeKb: DEFAULT_MAX_DIFF_SIZE_KB,
|
|
256
266
|
maxConsecutiveErrors: DEFAULT_MAX_CONSECUTIVE_ERRORS,
|
|
257
267
|
githubToken: null,
|
|
268
|
+
githubUsername: null,
|
|
258
269
|
codebaseDir: null,
|
|
259
270
|
agentCommand: null,
|
|
260
271
|
agents: null
|
|
@@ -273,6 +284,7 @@ function loadConfig() {
|
|
|
273
284
|
maxDiffSizeKb: overrides.maxDiffSizeKb ?? (typeof data.max_diff_size_kb === "number" ? data.max_diff_size_kb : DEFAULT_MAX_DIFF_SIZE_KB),
|
|
274
285
|
maxConsecutiveErrors: overrides.maxConsecutiveErrors ?? (typeof data.max_consecutive_errors === "number" ? data.max_consecutive_errors : DEFAULT_MAX_CONSECUTIVE_ERRORS),
|
|
275
286
|
githubToken: typeof data.github_token === "string" ? data.github_token : null,
|
|
287
|
+
githubUsername: typeof data.github_username === "string" ? data.github_username : null,
|
|
276
288
|
codebaseDir: typeof data.codebase_dir === "string" ? data.codebase_dir : null,
|
|
277
289
|
agentCommand: typeof data.agent_command === "string" ? data.agent_command : null,
|
|
278
290
|
agents: parseAgents(data)
|
|
@@ -289,6 +301,22 @@ function resolveCodebaseDir(agentDir, globalDir) {
|
|
|
289
301
|
}
|
|
290
302
|
return path.resolve(raw);
|
|
291
303
|
}
|
|
304
|
+
async function resolveGithubUsername(githubToken, fetchFn = fetch) {
|
|
305
|
+
if (!githubToken) return null;
|
|
306
|
+
try {
|
|
307
|
+
const response = await fetchFn("https://api.github.com/user", {
|
|
308
|
+
headers: {
|
|
309
|
+
Authorization: `Bearer ${githubToken}`,
|
|
310
|
+
Accept: "application/vnd.github+json"
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
if (!response.ok) return null;
|
|
314
|
+
const data = await response.json();
|
|
315
|
+
return typeof data.login === "string" ? data.login : null;
|
|
316
|
+
} catch {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
292
320
|
|
|
293
321
|
// src/codebase.ts
|
|
294
322
|
import { execFileSync } from "child_process";
|
|
@@ -498,15 +526,15 @@ function sleep(ms, signal) {
|
|
|
498
526
|
resolve2();
|
|
499
527
|
return;
|
|
500
528
|
}
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
);
|
|
529
|
+
const onAbort = () => {
|
|
530
|
+
clearTimeout(timer);
|
|
531
|
+
resolve2();
|
|
532
|
+
};
|
|
533
|
+
const timer = setTimeout(() => {
|
|
534
|
+
signal?.removeEventListener("abort", onAbort);
|
|
535
|
+
resolve2();
|
|
536
|
+
}, ms);
|
|
537
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
510
538
|
});
|
|
511
539
|
}
|
|
512
540
|
|
|
@@ -1311,6 +1339,11 @@ function toApiDiffUrl(webUrl) {
|
|
|
1311
1339
|
const [, owner, repo, prNumber] = match;
|
|
1312
1340
|
return `https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}`;
|
|
1313
1341
|
}
|
|
1342
|
+
function computeRoles(agent) {
|
|
1343
|
+
if (agent.review_only) return ["review"];
|
|
1344
|
+
if (agent.synthesizer_only) return ["summary"];
|
|
1345
|
+
return ["review", "summary"];
|
|
1346
|
+
}
|
|
1314
1347
|
async function fetchDiff(diffUrl, githubToken, signal) {
|
|
1315
1348
|
return withRetry(
|
|
1316
1349
|
async () => {
|
|
@@ -1344,7 +1377,17 @@ async function fetchDiff(diffUrl, githubToken, signal) {
|
|
|
1344
1377
|
}
|
|
1345
1378
|
var MAX_DIFF_FETCH_ATTEMPTS = 3;
|
|
1346
1379
|
async function pollLoop(client, agentId, reviewDeps, consumptionDeps, agentInfo, logger, agentSession, options) {
|
|
1347
|
-
const {
|
|
1380
|
+
const {
|
|
1381
|
+
pollIntervalMs,
|
|
1382
|
+
maxConsecutiveErrors,
|
|
1383
|
+
routerRelay,
|
|
1384
|
+
reviewOnly,
|
|
1385
|
+
repoConfig,
|
|
1386
|
+
roles,
|
|
1387
|
+
synthesizeRepos,
|
|
1388
|
+
githubUsername,
|
|
1389
|
+
signal
|
|
1390
|
+
} = options;
|
|
1348
1391
|
const { log, logError, logWarn } = logger;
|
|
1349
1392
|
log(`${icons.polling} Polling every ${pollIntervalMs / 1e3}s...`);
|
|
1350
1393
|
let consecutiveAuthErrors = 0;
|
|
@@ -1353,10 +1396,13 @@ async function pollLoop(client, agentId, reviewDeps, consumptionDeps, agentInfo,
|
|
|
1353
1396
|
while (!signal?.aborted) {
|
|
1354
1397
|
try {
|
|
1355
1398
|
const pollBody = { agent_id: agentId };
|
|
1399
|
+
if (githubUsername) pollBody.github_username = githubUsername;
|
|
1400
|
+
if (roles) pollBody.roles = roles;
|
|
1356
1401
|
if (reviewOnly) pollBody.review_only = true;
|
|
1357
|
-
if (repoConfig?.
|
|
1402
|
+
if (repoConfig?.list?.length) {
|
|
1358
1403
|
pollBody.repos = repoConfig.list;
|
|
1359
1404
|
}
|
|
1405
|
+
if (synthesizeRepos) pollBody.synthesize_repos = synthesizeRepos;
|
|
1360
1406
|
const pollResponse = await client.post("/api/tasks/poll", pollBody);
|
|
1361
1407
|
consecutiveAuthErrors = 0;
|
|
1362
1408
|
consecutiveErrors = 0;
|
|
@@ -1375,7 +1421,8 @@ async function pollLoop(client, agentId, reviewDeps, consumptionDeps, agentInfo,
|
|
|
1375
1421
|
logger,
|
|
1376
1422
|
agentSession,
|
|
1377
1423
|
routerRelay,
|
|
1378
|
-
signal
|
|
1424
|
+
signal,
|
|
1425
|
+
githubUsername
|
|
1379
1426
|
);
|
|
1380
1427
|
if (result.diffFetchFailed) {
|
|
1381
1428
|
agentSession.errorsEncountered++;
|
|
@@ -1428,20 +1475,22 @@ async function pollLoop(client, agentId, reviewDeps, consumptionDeps, agentInfo,
|
|
|
1428
1475
|
await sleep2(pollIntervalMs, signal);
|
|
1429
1476
|
}
|
|
1430
1477
|
}
|
|
1431
|
-
async function handleTask(client, agentId, task, reviewDeps, consumptionDeps, agentInfo, logger, agentSession, routerRelay, signal) {
|
|
1478
|
+
async function handleTask(client, agentId, task, reviewDeps, consumptionDeps, agentInfo, logger, agentSession, routerRelay, signal, githubUsername) {
|
|
1432
1479
|
const { task_id, owner, repo, pr_number, diff_url, timeout_seconds, prompt, role } = task;
|
|
1433
1480
|
const { log, logError, logWarn } = logger;
|
|
1434
1481
|
log(`${icons.success} Claimed task ${task_id} (${role}) \u2014 ${owner}/${repo}#${pr_number}`);
|
|
1435
1482
|
log(` https://github.com/${owner}/${repo}/pull/${pr_number}`);
|
|
1436
1483
|
let claimResponse;
|
|
1437
1484
|
try {
|
|
1485
|
+
const claimBody = {
|
|
1486
|
+
agent_id: agentId,
|
|
1487
|
+
role,
|
|
1488
|
+
model: agentInfo.model,
|
|
1489
|
+
tool: agentInfo.tool
|
|
1490
|
+
};
|
|
1491
|
+
if (githubUsername) claimBody.github_username = githubUsername;
|
|
1438
1492
|
claimResponse = await withRetry(
|
|
1439
|
-
() => client.post(`/api/tasks/${task_id}/claim`,
|
|
1440
|
-
agent_id: agentId,
|
|
1441
|
-
role,
|
|
1442
|
-
model: agentInfo.model,
|
|
1443
|
-
tool: agentInfo.tool
|
|
1444
|
-
}),
|
|
1493
|
+
() => client.post(`/api/tasks/${task_id}/claim`, claimBody),
|
|
1445
1494
|
{ maxAttempts: 2 },
|
|
1446
1495
|
signal
|
|
1447
1496
|
);
|
|
@@ -1798,15 +1847,15 @@ function sleep2(ms, signal) {
|
|
|
1798
1847
|
resolve2();
|
|
1799
1848
|
return;
|
|
1800
1849
|
}
|
|
1801
|
-
const
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
);
|
|
1850
|
+
const onAbort = () => {
|
|
1851
|
+
clearTimeout(timer);
|
|
1852
|
+
resolve2();
|
|
1853
|
+
};
|
|
1854
|
+
const timer = setTimeout(() => {
|
|
1855
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1856
|
+
resolve2();
|
|
1857
|
+
}, ms);
|
|
1858
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1810
1859
|
});
|
|
1811
1860
|
}
|
|
1812
1861
|
async function startAgent(agentId, platformUrl, agentInfo, reviewDeps, consumptionDeps, options) {
|
|
@@ -1844,6 +1893,9 @@ async function startAgent(agentId, platformUrl, agentInfo, reviewDeps, consumpti
|
|
|
1844
1893
|
routerRelay: options?.routerRelay,
|
|
1845
1894
|
reviewOnly: options?.reviewOnly,
|
|
1846
1895
|
repoConfig: options?.repoConfig,
|
|
1896
|
+
roles: options?.roles,
|
|
1897
|
+
synthesizeRepos: options?.synthesizeRepos,
|
|
1898
|
+
githubUsername: options?.githubUsername,
|
|
1847
1899
|
signal: abortController.signal
|
|
1848
1900
|
});
|
|
1849
1901
|
log(formatExitSummary(agentSession));
|
|
@@ -1865,6 +1917,10 @@ async function startAgentRouter() {
|
|
|
1865
1917
|
const auth = resolveGithubToken2(configToken);
|
|
1866
1918
|
const logger = createLogger(agentConfig?.name ?? "agent[0]");
|
|
1867
1919
|
logAuthMethod(auth.method, logger.log);
|
|
1920
|
+
const githubUsername = config.githubUsername ?? await resolveGithubUsername(auth.token) ?? void 0;
|
|
1921
|
+
if (githubUsername) {
|
|
1922
|
+
logger.log(`GitHub identity: ${githubUsername}`);
|
|
1923
|
+
}
|
|
1868
1924
|
const codebaseDir = resolveCodebaseDir(agentConfig?.codebase_dir, config.codebaseDir);
|
|
1869
1925
|
const reviewDeps = {
|
|
1870
1926
|
commandTemplate: commandTemplate ?? "",
|
|
@@ -1876,6 +1932,7 @@ async function startAgentRouter() {
|
|
|
1876
1932
|
const model = agentConfig?.model ?? "unknown";
|
|
1877
1933
|
const tool = agentConfig?.tool ?? "unknown";
|
|
1878
1934
|
const label = agentConfig?.name ?? "agent[0]";
|
|
1935
|
+
const roles = agentConfig ? computeRoles(agentConfig) : void 0;
|
|
1879
1936
|
await startAgent(
|
|
1880
1937
|
agentId,
|
|
1881
1938
|
config.platformUrl,
|
|
@@ -1890,12 +1947,15 @@ async function startAgentRouter() {
|
|
|
1890
1947
|
routerRelay: router,
|
|
1891
1948
|
reviewOnly: agentConfig?.review_only,
|
|
1892
1949
|
repoConfig: agentConfig?.repos,
|
|
1950
|
+
roles,
|
|
1951
|
+
synthesizeRepos: agentConfig?.synthesize_repos,
|
|
1952
|
+
githubUsername,
|
|
1893
1953
|
label
|
|
1894
1954
|
}
|
|
1895
1955
|
);
|
|
1896
1956
|
router.stop();
|
|
1897
1957
|
}
|
|
1898
|
-
function startAgentByIndex(config, agentIndex, pollIntervalMs, auth) {
|
|
1958
|
+
function startAgentByIndex(config, agentIndex, pollIntervalMs, auth, githubUsername) {
|
|
1899
1959
|
const agentId = crypto.randomUUID();
|
|
1900
1960
|
let commandTemplate;
|
|
1901
1961
|
let agentConfig;
|
|
@@ -1939,6 +1999,7 @@ function startAgentByIndex(config, agentIndex, pollIntervalMs, auth) {
|
|
|
1939
1999
|
const session = createSessionTracker();
|
|
1940
2000
|
const model = agentConfig?.model ?? "unknown";
|
|
1941
2001
|
const tool = agentConfig?.tool ?? "unknown";
|
|
2002
|
+
const roles = agentConfig ? computeRoles(agentConfig) : void 0;
|
|
1942
2003
|
const agentPromise = startAgent(
|
|
1943
2004
|
agentId,
|
|
1944
2005
|
config.platformUrl,
|
|
@@ -1951,6 +2012,9 @@ function startAgentByIndex(config, agentIndex, pollIntervalMs, auth) {
|
|
|
1951
2012
|
routerRelay,
|
|
1952
2013
|
reviewOnly: agentConfig?.review_only,
|
|
1953
2014
|
repoConfig: agentConfig?.repos,
|
|
2015
|
+
roles,
|
|
2016
|
+
synthesizeRepos: agentConfig?.synthesize_repos,
|
|
2017
|
+
githubUsername,
|
|
1954
2018
|
label
|
|
1955
2019
|
}
|
|
1956
2020
|
).finally(() => {
|
|
@@ -1965,6 +2029,10 @@ agentCommand.command("start").description("Start agents in polling mode").option
|
|
|
1965
2029
|
const configToken = resolveGithubToken(void 0, config.githubToken);
|
|
1966
2030
|
const auth = resolveGithubToken2(configToken);
|
|
1967
2031
|
logAuthMethod(auth.method, console.log.bind(console));
|
|
2032
|
+
const githubUsername = config.githubUsername ?? await resolveGithubUsername(auth.token) ?? void 0;
|
|
2033
|
+
if (githubUsername) {
|
|
2034
|
+
console.log(`GitHub identity: ${githubUsername}`);
|
|
2035
|
+
}
|
|
1968
2036
|
if (opts.all) {
|
|
1969
2037
|
if (!config.agents || config.agents.length === 0) {
|
|
1970
2038
|
console.error("No agents configured in ~/.opencara/config.yml");
|
|
@@ -1975,7 +2043,7 @@ agentCommand.command("start").description("Start agents in polling mode").option
|
|
|
1975
2043
|
const promises = [];
|
|
1976
2044
|
let startFailed = false;
|
|
1977
2045
|
for (let i = 0; i < config.agents.length; i++) {
|
|
1978
|
-
const p = startAgentByIndex(config, i, pollIntervalMs, auth);
|
|
2046
|
+
const p = startAgentByIndex(config, i, pollIntervalMs, auth, githubUsername);
|
|
1979
2047
|
if (p) {
|
|
1980
2048
|
promises.push(p);
|
|
1981
2049
|
} else {
|
|
@@ -2012,7 +2080,7 @@ agentCommand.command("start").description("Start agents in polling mode").option
|
|
|
2012
2080
|
process.exit(1);
|
|
2013
2081
|
return;
|
|
2014
2082
|
}
|
|
2015
|
-
const p = startAgentByIndex(config, agentIndex, pollIntervalMs, auth);
|
|
2083
|
+
const p = startAgentByIndex(config, agentIndex, pollIntervalMs, auth, githubUsername);
|
|
2016
2084
|
if (!p) {
|
|
2017
2085
|
process.exit(1);
|
|
2018
2086
|
return;
|
|
@@ -2022,7 +2090,7 @@ agentCommand.command("start").description("Start agents in polling mode").option
|
|
|
2022
2090
|
});
|
|
2023
2091
|
|
|
2024
2092
|
// src/index.ts
|
|
2025
|
-
var program = new Command2().name("opencara").description("OpenCara \u2014 distributed AI code review agent").version("0.
|
|
2093
|
+
var program = new Command2().name("opencara").description("OpenCara \u2014 distributed AI code review agent").version("0.13.0");
|
|
2026
2094
|
program.addCommand(agentCommand);
|
|
2027
2095
|
program.action(() => {
|
|
2028
2096
|
startAgentRouter();
|