opencara 0.12.1 → 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 +104 -38
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -137,20 +137,20 @@ 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 };
|
|
@@ -158,7 +158,7 @@ function parseRepoConfig(obj, index) {
|
|
|
158
158
|
if (mode === "whitelist" || mode === "blacklist") {
|
|
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
164
|
}
|
|
@@ -166,7 +166,7 @@ function parseRepoConfig(obj, index) {
|
|
|
166
166
|
for (let j = 0; j < list.length; j++) {
|
|
167
167
|
if (typeof list[j] !== "string" || !REPO_PATTERN.test(list[j])) {
|
|
168
168
|
throw new RepoConfigError(
|
|
169
|
-
`agents[${index}].
|
|
169
|
+
`agents[${index}].${field}.list[${j}] must match 'owner/repo' format`
|
|
170
170
|
);
|
|
171
171
|
}
|
|
172
172
|
}
|
|
@@ -213,10 +213,18 @@ function parseAgents(data) {
|
|
|
213
213
|
if (typeof obj.command === "string") agent.command = obj.command;
|
|
214
214
|
if (obj.router === true) agent.router = true;
|
|
215
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
|
+
}
|
|
216
222
|
if (typeof obj.github_token === "string") agent.github_token = obj.github_token;
|
|
217
223
|
if (typeof obj.codebase_dir === "string") agent.codebase_dir = obj.codebase_dir;
|
|
218
224
|
const repoConfig = parseRepoConfig(obj, i);
|
|
219
225
|
if (repoConfig) agent.repos = repoConfig;
|
|
226
|
+
const synthesizeRepoConfig = parseRepoConfig(obj, i, "synthesize_repos");
|
|
227
|
+
if (synthesizeRepoConfig) agent.synthesize_repos = synthesizeRepoConfig;
|
|
220
228
|
agents.push(agent);
|
|
221
229
|
}
|
|
222
230
|
return agents;
|
|
@@ -257,6 +265,7 @@ function loadConfig() {
|
|
|
257
265
|
maxDiffSizeKb: DEFAULT_MAX_DIFF_SIZE_KB,
|
|
258
266
|
maxConsecutiveErrors: DEFAULT_MAX_CONSECUTIVE_ERRORS,
|
|
259
267
|
githubToken: null,
|
|
268
|
+
githubUsername: null,
|
|
260
269
|
codebaseDir: null,
|
|
261
270
|
agentCommand: null,
|
|
262
271
|
agents: null
|
|
@@ -275,6 +284,7 @@ function loadConfig() {
|
|
|
275
284
|
maxDiffSizeKb: overrides.maxDiffSizeKb ?? (typeof data.max_diff_size_kb === "number" ? data.max_diff_size_kb : DEFAULT_MAX_DIFF_SIZE_KB),
|
|
276
285
|
maxConsecutiveErrors: overrides.maxConsecutiveErrors ?? (typeof data.max_consecutive_errors === "number" ? data.max_consecutive_errors : DEFAULT_MAX_CONSECUTIVE_ERRORS),
|
|
277
286
|
githubToken: typeof data.github_token === "string" ? data.github_token : null,
|
|
287
|
+
githubUsername: typeof data.github_username === "string" ? data.github_username : null,
|
|
278
288
|
codebaseDir: typeof data.codebase_dir === "string" ? data.codebase_dir : null,
|
|
279
289
|
agentCommand: typeof data.agent_command === "string" ? data.agent_command : null,
|
|
280
290
|
agents: parseAgents(data)
|
|
@@ -291,6 +301,22 @@ function resolveCodebaseDir(agentDir, globalDir) {
|
|
|
291
301
|
}
|
|
292
302
|
return path.resolve(raw);
|
|
293
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
|
+
}
|
|
294
320
|
|
|
295
321
|
// src/codebase.ts
|
|
296
322
|
import { execFileSync } from "child_process";
|
|
@@ -500,15 +526,15 @@ function sleep(ms, signal) {
|
|
|
500
526
|
resolve2();
|
|
501
527
|
return;
|
|
502
528
|
}
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
);
|
|
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 });
|
|
512
538
|
});
|
|
513
539
|
}
|
|
514
540
|
|
|
@@ -1313,6 +1339,11 @@ function toApiDiffUrl(webUrl) {
|
|
|
1313
1339
|
const [, owner, repo, prNumber] = match;
|
|
1314
1340
|
return `https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}`;
|
|
1315
1341
|
}
|
|
1342
|
+
function computeRoles(agent) {
|
|
1343
|
+
if (agent.review_only) return ["review"];
|
|
1344
|
+
if (agent.synthesizer_only) return ["summary"];
|
|
1345
|
+
return ["review", "summary"];
|
|
1346
|
+
}
|
|
1316
1347
|
async function fetchDiff(diffUrl, githubToken, signal) {
|
|
1317
1348
|
return withRetry(
|
|
1318
1349
|
async () => {
|
|
@@ -1346,7 +1377,17 @@ async function fetchDiff(diffUrl, githubToken, signal) {
|
|
|
1346
1377
|
}
|
|
1347
1378
|
var MAX_DIFF_FETCH_ATTEMPTS = 3;
|
|
1348
1379
|
async function pollLoop(client, agentId, reviewDeps, consumptionDeps, agentInfo, logger, agentSession, options) {
|
|
1349
|
-
const {
|
|
1380
|
+
const {
|
|
1381
|
+
pollIntervalMs,
|
|
1382
|
+
maxConsecutiveErrors,
|
|
1383
|
+
routerRelay,
|
|
1384
|
+
reviewOnly,
|
|
1385
|
+
repoConfig,
|
|
1386
|
+
roles,
|
|
1387
|
+
synthesizeRepos,
|
|
1388
|
+
githubUsername,
|
|
1389
|
+
signal
|
|
1390
|
+
} = options;
|
|
1350
1391
|
const { log, logError, logWarn } = logger;
|
|
1351
1392
|
log(`${icons.polling} Polling every ${pollIntervalMs / 1e3}s...`);
|
|
1352
1393
|
let consecutiveAuthErrors = 0;
|
|
@@ -1355,10 +1396,13 @@ async function pollLoop(client, agentId, reviewDeps, consumptionDeps, agentInfo,
|
|
|
1355
1396
|
while (!signal?.aborted) {
|
|
1356
1397
|
try {
|
|
1357
1398
|
const pollBody = { agent_id: agentId };
|
|
1399
|
+
if (githubUsername) pollBody.github_username = githubUsername;
|
|
1400
|
+
if (roles) pollBody.roles = roles;
|
|
1358
1401
|
if (reviewOnly) pollBody.review_only = true;
|
|
1359
1402
|
if (repoConfig?.list?.length) {
|
|
1360
1403
|
pollBody.repos = repoConfig.list;
|
|
1361
1404
|
}
|
|
1405
|
+
if (synthesizeRepos) pollBody.synthesize_repos = synthesizeRepos;
|
|
1362
1406
|
const pollResponse = await client.post("/api/tasks/poll", pollBody);
|
|
1363
1407
|
consecutiveAuthErrors = 0;
|
|
1364
1408
|
consecutiveErrors = 0;
|
|
@@ -1377,7 +1421,8 @@ async function pollLoop(client, agentId, reviewDeps, consumptionDeps, agentInfo,
|
|
|
1377
1421
|
logger,
|
|
1378
1422
|
agentSession,
|
|
1379
1423
|
routerRelay,
|
|
1380
|
-
signal
|
|
1424
|
+
signal,
|
|
1425
|
+
githubUsername
|
|
1381
1426
|
);
|
|
1382
1427
|
if (result.diffFetchFailed) {
|
|
1383
1428
|
agentSession.errorsEncountered++;
|
|
@@ -1430,20 +1475,22 @@ async function pollLoop(client, agentId, reviewDeps, consumptionDeps, agentInfo,
|
|
|
1430
1475
|
await sleep2(pollIntervalMs, signal);
|
|
1431
1476
|
}
|
|
1432
1477
|
}
|
|
1433
|
-
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) {
|
|
1434
1479
|
const { task_id, owner, repo, pr_number, diff_url, timeout_seconds, prompt, role } = task;
|
|
1435
1480
|
const { log, logError, logWarn } = logger;
|
|
1436
1481
|
log(`${icons.success} Claimed task ${task_id} (${role}) \u2014 ${owner}/${repo}#${pr_number}`);
|
|
1437
1482
|
log(` https://github.com/${owner}/${repo}/pull/${pr_number}`);
|
|
1438
1483
|
let claimResponse;
|
|
1439
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;
|
|
1440
1492
|
claimResponse = await withRetry(
|
|
1441
|
-
() => client.post(`/api/tasks/${task_id}/claim`,
|
|
1442
|
-
agent_id: agentId,
|
|
1443
|
-
role,
|
|
1444
|
-
model: agentInfo.model,
|
|
1445
|
-
tool: agentInfo.tool
|
|
1446
|
-
}),
|
|
1493
|
+
() => client.post(`/api/tasks/${task_id}/claim`, claimBody),
|
|
1447
1494
|
{ maxAttempts: 2 },
|
|
1448
1495
|
signal
|
|
1449
1496
|
);
|
|
@@ -1800,15 +1847,15 @@ function sleep2(ms, signal) {
|
|
|
1800
1847
|
resolve2();
|
|
1801
1848
|
return;
|
|
1802
1849
|
}
|
|
1803
|
-
const
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
);
|
|
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 });
|
|
1812
1859
|
});
|
|
1813
1860
|
}
|
|
1814
1861
|
async function startAgent(agentId, platformUrl, agentInfo, reviewDeps, consumptionDeps, options) {
|
|
@@ -1846,6 +1893,9 @@ async function startAgent(agentId, platformUrl, agentInfo, reviewDeps, consumpti
|
|
|
1846
1893
|
routerRelay: options?.routerRelay,
|
|
1847
1894
|
reviewOnly: options?.reviewOnly,
|
|
1848
1895
|
repoConfig: options?.repoConfig,
|
|
1896
|
+
roles: options?.roles,
|
|
1897
|
+
synthesizeRepos: options?.synthesizeRepos,
|
|
1898
|
+
githubUsername: options?.githubUsername,
|
|
1849
1899
|
signal: abortController.signal
|
|
1850
1900
|
});
|
|
1851
1901
|
log(formatExitSummary(agentSession));
|
|
@@ -1867,6 +1917,10 @@ async function startAgentRouter() {
|
|
|
1867
1917
|
const auth = resolveGithubToken2(configToken);
|
|
1868
1918
|
const logger = createLogger(agentConfig?.name ?? "agent[0]");
|
|
1869
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
|
+
}
|
|
1870
1924
|
const codebaseDir = resolveCodebaseDir(agentConfig?.codebase_dir, config.codebaseDir);
|
|
1871
1925
|
const reviewDeps = {
|
|
1872
1926
|
commandTemplate: commandTemplate ?? "",
|
|
@@ -1878,6 +1932,7 @@ async function startAgentRouter() {
|
|
|
1878
1932
|
const model = agentConfig?.model ?? "unknown";
|
|
1879
1933
|
const tool = agentConfig?.tool ?? "unknown";
|
|
1880
1934
|
const label = agentConfig?.name ?? "agent[0]";
|
|
1935
|
+
const roles = agentConfig ? computeRoles(agentConfig) : void 0;
|
|
1881
1936
|
await startAgent(
|
|
1882
1937
|
agentId,
|
|
1883
1938
|
config.platformUrl,
|
|
@@ -1892,12 +1947,15 @@ async function startAgentRouter() {
|
|
|
1892
1947
|
routerRelay: router,
|
|
1893
1948
|
reviewOnly: agentConfig?.review_only,
|
|
1894
1949
|
repoConfig: agentConfig?.repos,
|
|
1950
|
+
roles,
|
|
1951
|
+
synthesizeRepos: agentConfig?.synthesize_repos,
|
|
1952
|
+
githubUsername,
|
|
1895
1953
|
label
|
|
1896
1954
|
}
|
|
1897
1955
|
);
|
|
1898
1956
|
router.stop();
|
|
1899
1957
|
}
|
|
1900
|
-
function startAgentByIndex(config, agentIndex, pollIntervalMs, auth) {
|
|
1958
|
+
function startAgentByIndex(config, agentIndex, pollIntervalMs, auth, githubUsername) {
|
|
1901
1959
|
const agentId = crypto.randomUUID();
|
|
1902
1960
|
let commandTemplate;
|
|
1903
1961
|
let agentConfig;
|
|
@@ -1941,6 +1999,7 @@ function startAgentByIndex(config, agentIndex, pollIntervalMs, auth) {
|
|
|
1941
1999
|
const session = createSessionTracker();
|
|
1942
2000
|
const model = agentConfig?.model ?? "unknown";
|
|
1943
2001
|
const tool = agentConfig?.tool ?? "unknown";
|
|
2002
|
+
const roles = agentConfig ? computeRoles(agentConfig) : void 0;
|
|
1944
2003
|
const agentPromise = startAgent(
|
|
1945
2004
|
agentId,
|
|
1946
2005
|
config.platformUrl,
|
|
@@ -1953,6 +2012,9 @@ function startAgentByIndex(config, agentIndex, pollIntervalMs, auth) {
|
|
|
1953
2012
|
routerRelay,
|
|
1954
2013
|
reviewOnly: agentConfig?.review_only,
|
|
1955
2014
|
repoConfig: agentConfig?.repos,
|
|
2015
|
+
roles,
|
|
2016
|
+
synthesizeRepos: agentConfig?.synthesize_repos,
|
|
2017
|
+
githubUsername,
|
|
1956
2018
|
label
|
|
1957
2019
|
}
|
|
1958
2020
|
).finally(() => {
|
|
@@ -1967,6 +2029,10 @@ agentCommand.command("start").description("Start agents in polling mode").option
|
|
|
1967
2029
|
const configToken = resolveGithubToken(void 0, config.githubToken);
|
|
1968
2030
|
const auth = resolveGithubToken2(configToken);
|
|
1969
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
|
+
}
|
|
1970
2036
|
if (opts.all) {
|
|
1971
2037
|
if (!config.agents || config.agents.length === 0) {
|
|
1972
2038
|
console.error("No agents configured in ~/.opencara/config.yml");
|
|
@@ -1977,7 +2043,7 @@ agentCommand.command("start").description("Start agents in polling mode").option
|
|
|
1977
2043
|
const promises = [];
|
|
1978
2044
|
let startFailed = false;
|
|
1979
2045
|
for (let i = 0; i < config.agents.length; i++) {
|
|
1980
|
-
const p = startAgentByIndex(config, i, pollIntervalMs, auth);
|
|
2046
|
+
const p = startAgentByIndex(config, i, pollIntervalMs, auth, githubUsername);
|
|
1981
2047
|
if (p) {
|
|
1982
2048
|
promises.push(p);
|
|
1983
2049
|
} else {
|
|
@@ -2014,7 +2080,7 @@ agentCommand.command("start").description("Start agents in polling mode").option
|
|
|
2014
2080
|
process.exit(1);
|
|
2015
2081
|
return;
|
|
2016
2082
|
}
|
|
2017
|
-
const p = startAgentByIndex(config, agentIndex, pollIntervalMs, auth);
|
|
2083
|
+
const p = startAgentByIndex(config, agentIndex, pollIntervalMs, auth, githubUsername);
|
|
2018
2084
|
if (!p) {
|
|
2019
2085
|
process.exit(1);
|
|
2020
2086
|
return;
|
|
@@ -2024,7 +2090,7 @@ agentCommand.command("start").description("Start agents in polling mode").option
|
|
|
2024
2090
|
});
|
|
2025
2091
|
|
|
2026
2092
|
// src/index.ts
|
|
2027
|
-
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");
|
|
2028
2094
|
program.addCommand(agentCommand);
|
|
2029
2095
|
program.action(() => {
|
|
2030
2096
|
startAgentRouter();
|