opencara 0.20.0 → 0.21.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 +157 -83
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -240,6 +240,9 @@ var DEFAULT_TRIAGE_TRIGGER = {
|
|
|
240
240
|
events: ["opened"],
|
|
241
241
|
comment: "/opencara triage"
|
|
242
242
|
};
|
|
243
|
+
var DEFAULT_ISSUE_REVIEW_TRIGGER = {
|
|
244
|
+
comment: "/opencara review-issue"
|
|
245
|
+
};
|
|
243
246
|
var DEFAULT_TRIGGER = DEFAULT_REVIEW_TRIGGER;
|
|
244
247
|
var DEFAULT_FEATURE_CONFIG = {
|
|
245
248
|
prompt: "Review this pull request for bugs, security issues, and code quality.",
|
|
@@ -308,6 +311,24 @@ function parseAgentSlots(value) {
|
|
|
308
311
|
}
|
|
309
312
|
return slots.length > 0 ? slots : void 0;
|
|
310
313
|
}
|
|
314
|
+
function parseNamedAgents(value) {
|
|
315
|
+
if (!Array.isArray(value))
|
|
316
|
+
return void 0;
|
|
317
|
+
const agents = [];
|
|
318
|
+
for (const item of value) {
|
|
319
|
+
if (!isObject(item))
|
|
320
|
+
continue;
|
|
321
|
+
if (typeof item.id !== "string" || typeof item.prompt !== "string")
|
|
322
|
+
continue;
|
|
323
|
+
const agent = { id: item.id, prompt: item.prompt };
|
|
324
|
+
if (typeof item.model === "string")
|
|
325
|
+
agent.model = item.model;
|
|
326
|
+
if (typeof item.tool === "string")
|
|
327
|
+
agent.tool = item.tool;
|
|
328
|
+
agents.push(agent);
|
|
329
|
+
}
|
|
330
|
+
return agents.length > 0 ? agents : void 0;
|
|
331
|
+
}
|
|
311
332
|
function parseFeatureFields(raw, defaults) {
|
|
312
333
|
const agentSlots = parseAgentSlots(raw.agents);
|
|
313
334
|
return {
|
|
@@ -408,12 +429,16 @@ var DEFAULT_IMPLEMENT_FEATURE = {
|
|
|
408
429
|
modelDiversityGraceMs: DEFAULT_MODEL_DIVERSITY_GRACE_MS
|
|
409
430
|
};
|
|
410
431
|
function parseImplementSection(raw) {
|
|
411
|
-
const base = parseFeatureFields(raw, DEFAULT_IMPLEMENT_FEATURE);
|
|
432
|
+
const { agents: _slots, ...base } = parseFeatureFields(raw, DEFAULT_IMPLEMENT_FEATURE);
|
|
412
433
|
const triggerRaw = isObject(raw.trigger) ? raw.trigger : void 0;
|
|
434
|
+
const namedAgents = parseNamedAgents(raw.agents);
|
|
435
|
+
const agentField = typeof raw.agent_field === "string" ? raw.agent_field : void 0;
|
|
413
436
|
return {
|
|
414
437
|
...base,
|
|
415
438
|
enabled: typeof raw.enabled === "boolean" ? raw.enabled : true,
|
|
416
|
-
trigger: parseTriggerSection(triggerRaw, DEFAULT_IMPLEMENT_TRIGGER)
|
|
439
|
+
trigger: parseTriggerSection(triggerRaw, DEFAULT_IMPLEMENT_TRIGGER),
|
|
440
|
+
...namedAgents ? { agents: namedAgents } : {},
|
|
441
|
+
...agentField ? { agent_field: agentField } : {}
|
|
417
442
|
};
|
|
418
443
|
}
|
|
419
444
|
var DEFAULT_FIX_FEATURE = {
|
|
@@ -425,12 +450,33 @@ var DEFAULT_FIX_FEATURE = {
|
|
|
425
450
|
modelDiversityGraceMs: DEFAULT_MODEL_DIVERSITY_GRACE_MS
|
|
426
451
|
};
|
|
427
452
|
function parseFixSection(raw) {
|
|
428
|
-
const base = parseFeatureFields(raw, DEFAULT_FIX_FEATURE);
|
|
453
|
+
const { agents: _slots, ...base } = parseFeatureFields(raw, DEFAULT_FIX_FEATURE);
|
|
454
|
+
const triggerRaw = isObject(raw.trigger) ? raw.trigger : void 0;
|
|
455
|
+
const namedAgents = parseNamedAgents(raw.agents);
|
|
456
|
+
const agentField = typeof raw.agent_field === "string" ? raw.agent_field : void 0;
|
|
457
|
+
return {
|
|
458
|
+
...base,
|
|
459
|
+
enabled: typeof raw.enabled === "boolean" ? raw.enabled : true,
|
|
460
|
+
trigger: parseTriggerSection(triggerRaw, DEFAULT_FIX_TRIGGER),
|
|
461
|
+
...namedAgents ? { agents: namedAgents } : {},
|
|
462
|
+
...agentField ? { agent_field: agentField } : {}
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
var DEFAULT_ISSUE_REVIEW_FEATURE = {
|
|
466
|
+
prompt: "Review this issue for clarity, completeness, and actionability.",
|
|
467
|
+
agentCount: 2,
|
|
468
|
+
timeout: "5m",
|
|
469
|
+
preferredModels: [],
|
|
470
|
+
preferredTools: [],
|
|
471
|
+
modelDiversityGraceMs: DEFAULT_MODEL_DIVERSITY_GRACE_MS
|
|
472
|
+
};
|
|
473
|
+
function parseIssueReviewSection(raw) {
|
|
474
|
+
const base = parseFeatureFields(raw, DEFAULT_ISSUE_REVIEW_FEATURE);
|
|
429
475
|
const triggerRaw = isObject(raw.trigger) ? raw.trigger : void 0;
|
|
430
476
|
return {
|
|
431
477
|
...base,
|
|
432
478
|
enabled: typeof raw.enabled === "boolean" ? raw.enabled : true,
|
|
433
|
-
trigger: parseTriggerSection(triggerRaw,
|
|
479
|
+
trigger: parseTriggerSection(triggerRaw, DEFAULT_ISSUE_REVIEW_TRIGGER)
|
|
434
480
|
};
|
|
435
481
|
}
|
|
436
482
|
function parseOpenCaraConfig(toml) {
|
|
@@ -473,6 +519,9 @@ function parseOpenCaraConfig(toml) {
|
|
|
473
519
|
if (isObject(raw.fix)) {
|
|
474
520
|
config.fix = parseFixSection(raw.fix);
|
|
475
521
|
}
|
|
522
|
+
if (isObject(raw.issue_review)) {
|
|
523
|
+
config.issue_review = parseIssueReviewSection(raw.issue_review);
|
|
524
|
+
}
|
|
476
525
|
return config;
|
|
477
526
|
}
|
|
478
527
|
function parseLegacyReviewConfig(raw) {
|
|
@@ -2125,15 +2174,10 @@ ${reviewSections}`);
|
|
|
2125
2174
|
}
|
|
2126
2175
|
var TRIAGE_SYSTEM_PROMPT = `You are a triage agent for a software project. Your job is to analyze a GitHub issue and produce a structured triage report.
|
|
2127
2176
|
|
|
2128
|
-
The project is a monorepo with the following packages:
|
|
2129
|
-
- server \u2014 Hono server on Cloudflare Workers (webhook receiver, REST task API, GitHub integration)
|
|
2130
|
-
- cli \u2014 Agent CLI npm package (HTTP polling, local review execution, router mode)
|
|
2131
|
-
- shared \u2014 Shared TypeScript types (REST API contracts, review config parser)
|
|
2132
|
-
|
|
2133
2177
|
## Instructions
|
|
2134
2178
|
|
|
2135
2179
|
1. **Categorize** the issue into one of: bug, feature, improvement, question, docs, chore
|
|
2136
|
-
2. **Identify the module** most relevant to this issue
|
|
2180
|
+
2. **Identify the module** most relevant to this issue (use the most appropriate component, package, or area name from the repository \u2014 or omit if unclear)
|
|
2137
2181
|
3. **Assess priority**: critical (service down / data loss), high (blocks users), medium (important but not urgent), low (nice to have)
|
|
2138
2182
|
4. **Estimate size**: XS (< 1hr), S (1-4hr), M (4hr-2d), L (2-5d), XL (> 5d)
|
|
2139
2183
|
5. **Suggest labels** relevant to the issue (e.g., "bug", "enhancement", "docs", module names, etc.)
|
|
@@ -2148,7 +2192,7 @@ Respond with ONLY a JSON object (no markdown fences, no preamble, no explanation
|
|
|
2148
2192
|
\`\`\`
|
|
2149
2193
|
{
|
|
2150
2194
|
"category": "bug" | "feature" | "improvement" | "question" | "docs" | "chore",
|
|
2151
|
-
"module": "
|
|
2195
|
+
"module": "<string \u2014 component, package, or area name from the repository>",
|
|
2152
2196
|
"priority": "critical" | "high" | "medium" | "low",
|
|
2153
2197
|
"size": "XS" | "S" | "M" | "L" | "XL",
|
|
2154
2198
|
"labels": ["label1", "label2"],
|
|
@@ -4325,6 +4369,34 @@ var DEFAULT_RECHECK_INTERVAL = 50;
|
|
|
4325
4369
|
var DEFAULT_POLL_INTERVAL_MS = 1e4;
|
|
4326
4370
|
var MAX_CONSECUTIVE_AUTH_ERRORS = 3;
|
|
4327
4371
|
var MAX_POLL_BACKOFF_MS = 3e5;
|
|
4372
|
+
var SHUTDOWN_GRACE_MS = 5e3;
|
|
4373
|
+
function registerShutdownHandlers(controller, log, graceMs = SHUTDOWN_GRACE_MS) {
|
|
4374
|
+
let shutdownInitiated = false;
|
|
4375
|
+
let forceTimer;
|
|
4376
|
+
const onSignal = (signal) => {
|
|
4377
|
+
if (shutdownInitiated) {
|
|
4378
|
+
log(`${icons.stop} Received ${signal} again \u2014 forcing exit`);
|
|
4379
|
+
process.exit(1);
|
|
4380
|
+
}
|
|
4381
|
+
shutdownInitiated = true;
|
|
4382
|
+
log(`${icons.stop} Received ${signal} \u2014 shutting down gracefully...`);
|
|
4383
|
+
controller.abort();
|
|
4384
|
+
forceTimer = setTimeout(() => {
|
|
4385
|
+
log(`${icons.stop} Shutdown timed out after ${graceMs / 1e3}s \u2014 forcing exit`);
|
|
4386
|
+
process.exit(1);
|
|
4387
|
+
}, graceMs);
|
|
4388
|
+
forceTimer.unref();
|
|
4389
|
+
};
|
|
4390
|
+
const onSigint = () => onSignal("SIGINT");
|
|
4391
|
+
const onSigterm = () => onSignal("SIGTERM");
|
|
4392
|
+
process.on("SIGINT", onSigint);
|
|
4393
|
+
process.on("SIGTERM", onSigterm);
|
|
4394
|
+
return () => {
|
|
4395
|
+
process.removeListener("SIGINT", onSigint);
|
|
4396
|
+
process.removeListener("SIGTERM", onSigterm);
|
|
4397
|
+
if (forceTimer) clearTimeout(forceTimer);
|
|
4398
|
+
};
|
|
4399
|
+
}
|
|
4328
4400
|
var NON_RETRYABLE_STATUSES = /* @__PURE__ */ new Set([401, 403, 404]);
|
|
4329
4401
|
function toApiDiffUrl(webUrl) {
|
|
4330
4402
|
const match = webUrl.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)(?:\.diff)?$/);
|
|
@@ -5318,7 +5390,7 @@ function sleep2(ms, signal) {
|
|
|
5318
5390
|
async function startAgent(agentId, platformUrl, agentInfo, reviewDeps, consumptionDeps, options) {
|
|
5319
5391
|
const client = new ApiClient(platformUrl, {
|
|
5320
5392
|
authToken: options?.authToken,
|
|
5321
|
-
cliVersion: "0.
|
|
5393
|
+
cliVersion: "0.21.0",
|
|
5322
5394
|
versionOverride: options?.versionOverride,
|
|
5323
5395
|
onTokenRefresh: options?.onTokenRefresh
|
|
5324
5396
|
});
|
|
@@ -5370,44 +5442,43 @@ async function startAgent(agentId, platformUrl, agentInfo, reviewDeps, consumpti
|
|
|
5370
5442
|
}
|
|
5371
5443
|
const cleanupTracker = ttlMs > 0 ? new CodebaseCleanupTracker(ttlMs) : void 0;
|
|
5372
5444
|
const abortController = new AbortController();
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5445
|
+
const removeShutdownHandlers = registerShutdownHandlers(abortController, log);
|
|
5446
|
+
try {
|
|
5447
|
+
await pollLoop(client, agentId, reviewDeps, deps, agentInfo, logger, agentSession, {
|
|
5448
|
+
pollIntervalMs: options?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
|
|
5449
|
+
maxConsecutiveErrors: options?.maxConsecutiveErrors ?? DEFAULT_MAX_CONSECUTIVE_ERRORS,
|
|
5450
|
+
routerRelay: options?.routerRelay,
|
|
5451
|
+
reviewOnly: options?.reviewOnly,
|
|
5452
|
+
repoConfig: options?.repoConfig,
|
|
5453
|
+
roles: options?.roles,
|
|
5454
|
+
synthesizeRepos: options?.synthesizeRepos,
|
|
5455
|
+
signal: abortController.signal,
|
|
5456
|
+
cleanupTracker,
|
|
5457
|
+
verbose: options?.verbose,
|
|
5458
|
+
agentOwner: options?.agentOwner,
|
|
5459
|
+
userOrgs: options?.userOrgs
|
|
5460
|
+
});
|
|
5461
|
+
if (cleanupTracker && cleanupTracker.size > 0) {
|
|
5462
|
+
const finalSwept = await cleanupTracker.sweep(cleanupWorktree);
|
|
5463
|
+
if (finalSwept > 0) {
|
|
5464
|
+
log(
|
|
5465
|
+
`${icons.info} Cleaned up ${finalSwept} codebase director${finalSwept === 1 ? "y" : "ies"} on shutdown`
|
|
5466
|
+
);
|
|
5467
|
+
}
|
|
5468
|
+
}
|
|
5469
|
+
if (deps.usageTracker) {
|
|
5396
5470
|
log(
|
|
5397
|
-
|
|
5471
|
+
deps.usageTracker.formatSummary(
|
|
5472
|
+
deps.usageLimits ?? usageLimits,
|
|
5473
|
+
deps.agentLimits,
|
|
5474
|
+
deps.agentId
|
|
5475
|
+
)
|
|
5398
5476
|
);
|
|
5399
5477
|
}
|
|
5478
|
+
log(formatExitSummary(agentSession));
|
|
5479
|
+
} finally {
|
|
5480
|
+
removeShutdownHandlers();
|
|
5400
5481
|
}
|
|
5401
|
-
if (deps.usageTracker) {
|
|
5402
|
-
log(
|
|
5403
|
-
deps.usageTracker.formatSummary(
|
|
5404
|
-
deps.usageLimits ?? usageLimits,
|
|
5405
|
-
deps.agentLimits,
|
|
5406
|
-
deps.agentId
|
|
5407
|
-
)
|
|
5408
|
-
);
|
|
5409
|
-
}
|
|
5410
|
-
log(formatExitSummary(agentSession));
|
|
5411
5482
|
}
|
|
5412
5483
|
async function batchPollLoop(client, agentStates, options) {
|
|
5413
5484
|
const {
|
|
@@ -5605,7 +5676,7 @@ async function startBatchAgents(config, agents, pollIntervalMs, oauthToken, opti
|
|
|
5605
5676
|
const { versionOverride, verbose, instancesOverride, agentOwner, userOrgs } = options;
|
|
5606
5677
|
const client = new ApiClient(config.platformUrl, {
|
|
5607
5678
|
authToken: oauthToken,
|
|
5608
|
-
cliVersion: "0.
|
|
5679
|
+
cliVersion: "0.21.0",
|
|
5609
5680
|
versionOverride,
|
|
5610
5681
|
onTokenRefresh: () => getValidToken(config.platformUrl, { configPath: config.authFile })
|
|
5611
5682
|
});
|
|
@@ -5726,45 +5797,48 @@ async function startBatchAgents(config, agents, pollIntervalMs, oauthToken, opti
|
|
|
5726
5797
|
}
|
|
5727
5798
|
}
|
|
5728
5799
|
const abortController = new AbortController();
|
|
5729
|
-
|
|
5730
|
-
process.on("SIGTERM", () => abortController.abort());
|
|
5800
|
+
const removeShutdownHandlers = registerShutdownHandlers(abortController, log);
|
|
5731
5801
|
log(`${agentStates.length} agent instance(s) running in batch mode. Press Ctrl+C to stop.
|
|
5732
5802
|
`);
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5803
|
+
try {
|
|
5804
|
+
await batchPollLoop(client, agentStates, {
|
|
5805
|
+
pollIntervalMs,
|
|
5806
|
+
maxConsecutiveErrors: config.maxConsecutiveErrors,
|
|
5807
|
+
signal: abortController.signal,
|
|
5808
|
+
accessibleRepos,
|
|
5809
|
+
githubToken: oauthToken
|
|
5810
|
+
});
|
|
5811
|
+
await Promise.allSettled(
|
|
5812
|
+
agentStates.map(async (state) => {
|
|
5813
|
+
state.routerRelay?.stop();
|
|
5814
|
+
if (state.cleanupTracker && state.cleanupTracker.size > 0) {
|
|
5815
|
+
const swept = await state.cleanupTracker.sweep(cleanupWorktree);
|
|
5816
|
+
if (swept > 0) {
|
|
5817
|
+
state.logger.log(
|
|
5818
|
+
`${icons.info} Cleaned up ${swept} codebase director${swept === 1 ? "y" : "ies"} on shutdown`
|
|
5819
|
+
);
|
|
5820
|
+
}
|
|
5821
|
+
}
|
|
5822
|
+
if (state.consumptionDeps.usageTracker) {
|
|
5823
|
+
const limits = state.consumptionDeps.usageLimits ?? {
|
|
5824
|
+
maxTasksPerDay: null,
|
|
5825
|
+
maxTokensPerDay: null,
|
|
5826
|
+
maxTokensPerReview: null
|
|
5827
|
+
};
|
|
5746
5828
|
state.logger.log(
|
|
5747
|
-
|
|
5829
|
+
state.consumptionDeps.usageTracker.formatSummary(
|
|
5830
|
+
limits,
|
|
5831
|
+
state.consumptionDeps.agentLimits,
|
|
5832
|
+
state.consumptionDeps.agentId
|
|
5833
|
+
)
|
|
5748
5834
|
);
|
|
5749
5835
|
}
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
};
|
|
5757
|
-
state.logger.log(
|
|
5758
|
-
state.consumptionDeps.usageTracker.formatSummary(
|
|
5759
|
-
limits,
|
|
5760
|
-
state.consumptionDeps.agentLimits,
|
|
5761
|
-
state.consumptionDeps.agentId
|
|
5762
|
-
)
|
|
5763
|
-
);
|
|
5764
|
-
}
|
|
5765
|
-
state.logger.log(formatExitSummary(state.agentSession));
|
|
5766
|
-
})
|
|
5767
|
-
);
|
|
5836
|
+
state.logger.log(formatExitSummary(state.agentSession));
|
|
5837
|
+
})
|
|
5838
|
+
);
|
|
5839
|
+
} finally {
|
|
5840
|
+
removeShutdownHandlers();
|
|
5841
|
+
}
|
|
5768
5842
|
}
|
|
5769
5843
|
async function startAgentRouter() {
|
|
5770
5844
|
const config = loadConfig();
|
|
@@ -5945,7 +6019,7 @@ agentCommand.command("start").description("Start agents in polling mode").option
|
|
|
5945
6019
|
}
|
|
5946
6020
|
config = loadConfig();
|
|
5947
6021
|
}
|
|
5948
|
-
console.log(formatVersionBanner("0.
|
|
6022
|
+
console.log(formatVersionBanner("0.21.0", "a24bc6d"));
|
|
5949
6023
|
if (config.agents && config.agents.length > 0) {
|
|
5950
6024
|
const toolEntries = config.agents.map((a) => ({
|
|
5951
6025
|
tool: a.tool,
|
|
@@ -6768,7 +6842,7 @@ var statusCommand = new Command4("status").description("Show agent config, conne
|
|
|
6768
6842
|
});
|
|
6769
6843
|
|
|
6770
6844
|
// src/index.ts
|
|
6771
|
-
var program = new Command5().name("opencara").description("OpenCara \u2014 distributed AI code review agent").version(`${"0.
|
|
6845
|
+
var program = new Command5().name("opencara").description("OpenCara \u2014 distributed AI code review agent").version(`${"0.21.0"} (${"a24bc6d"})`);
|
|
6772
6846
|
program.addCommand(agentCommand);
|
|
6773
6847
|
program.addCommand(authCommand());
|
|
6774
6848
|
program.addCommand(dedupCommand());
|