opencara 0.25.1 → 0.25.2
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 -33
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -914,7 +914,27 @@ var ToolTimeoutError = class extends Error {
|
|
|
914
914
|
var SIGKILL_GRACE_MS = 5e3;
|
|
915
915
|
var MIN_PARTIAL_RESULT_LENGTH = 50;
|
|
916
916
|
var STDOUT_LIVENESS_TIMEOUT_MS = 3e5;
|
|
917
|
+
var DEFAULT_HEARTBEAT_INTERVAL_MS = 6e4;
|
|
917
918
|
var MAX_STDERR_LENGTH = 1e3;
|
|
919
|
+
function startHeartbeatTimer(heartbeat, isSettled = () => false) {
|
|
920
|
+
if (!heartbeat) return () => {
|
|
921
|
+
};
|
|
922
|
+
const intervalMs = heartbeat.intervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
|
|
923
|
+
if (intervalMs <= 0) return () => {
|
|
924
|
+
};
|
|
925
|
+
const timer = setInterval(() => {
|
|
926
|
+
if (isSettled()) return;
|
|
927
|
+
try {
|
|
928
|
+
const r = heartbeat.callback();
|
|
929
|
+
if (r && typeof r.catch === "function") {
|
|
930
|
+
r.catch(() => {
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
} catch {
|
|
934
|
+
}
|
|
935
|
+
}, intervalMs);
|
|
936
|
+
return () => clearInterval(timer);
|
|
937
|
+
}
|
|
918
938
|
function validateCommandBinary(commandTemplate) {
|
|
919
939
|
const { command } = parseCommandTemplate(commandTemplate);
|
|
920
940
|
if (path3.isAbsolute(command)) {
|
|
@@ -1010,7 +1030,7 @@ function parseTokenUsage(stdout, stderr) {
|
|
|
1010
1030
|
const estimated = estimateTokens(stdout);
|
|
1011
1031
|
return { tokens: estimated, parsed: false, input: 0, output: estimated };
|
|
1012
1032
|
}
|
|
1013
|
-
function executeTool(commandTemplate, prompt2, timeoutMs, signal, vars, cwd, livenessTimeoutMs) {
|
|
1033
|
+
function executeTool(commandTemplate, prompt2, timeoutMs, signal, vars, cwd, livenessTimeoutMs, heartbeat) {
|
|
1014
1034
|
const promptViaArg = commandTemplate.includes("${PROMPT}");
|
|
1015
1035
|
const allVars = { ...vars, PROMPT: prompt2 };
|
|
1016
1036
|
if (cwd && !allVars["CODEBASE_DIR"]) {
|
|
@@ -1054,6 +1074,7 @@ function executeTool(commandTemplate, prompt2, timeoutMs, signal, vars, cwd, liv
|
|
|
1054
1074
|
}
|
|
1055
1075
|
}, effectiveLivenessMs);
|
|
1056
1076
|
}
|
|
1077
|
+
const stopHeartbeat = startHeartbeatTimer(heartbeat, () => settled);
|
|
1057
1078
|
child.stdout?.on("data", (chunk) => {
|
|
1058
1079
|
stdout += chunk.toString();
|
|
1059
1080
|
if (livenessTimer) {
|
|
@@ -1082,6 +1103,7 @@ function executeTool(commandTemplate, prompt2, timeoutMs, signal, vars, cwd, liv
|
|
|
1082
1103
|
clearTimeout(timer);
|
|
1083
1104
|
if (livenessTimer) clearTimeout(livenessTimer);
|
|
1084
1105
|
if (sigkillTimer) clearTimeout(sigkillTimer);
|
|
1106
|
+
stopHeartbeat();
|
|
1085
1107
|
if (onAbort && signal) {
|
|
1086
1108
|
signal.removeEventListener("abort", onAbort);
|
|
1087
1109
|
}
|
|
@@ -1697,7 +1719,8 @@ ${userMessage}`;
|
|
|
1697
1719
|
abortController.signal,
|
|
1698
1720
|
void 0,
|
|
1699
1721
|
deps.codebaseDir ?? void 0,
|
|
1700
|
-
deps.livenessTimeoutMs
|
|
1722
|
+
deps.livenessTimeoutMs,
|
|
1723
|
+
deps.heartbeat
|
|
1701
1724
|
);
|
|
1702
1725
|
const { verdict, review } = extractVerdict(result.stdout);
|
|
1703
1726
|
const inputTokens = result.tokensParsed ? 0 : estimateTokens(fullPrompt);
|
|
@@ -2705,7 +2728,8 @@ ${userMessage}`;
|
|
|
2705
2728
|
abortController.signal,
|
|
2706
2729
|
void 0,
|
|
2707
2730
|
deps.codebaseDir ?? void 0,
|
|
2708
|
-
deps.livenessTimeoutMs
|
|
2731
|
+
deps.livenessTimeoutMs,
|
|
2732
|
+
deps.heartbeat
|
|
2709
2733
|
);
|
|
2710
2734
|
const { verdict, review } = extractVerdict(result.stdout);
|
|
2711
2735
|
const flaggedReviews = extractFlaggedReviews(result.stdout);
|
|
@@ -4046,7 +4070,7 @@ function createPR(worktreePath, issueNumber, issueTitle, summary, branchName) {
|
|
|
4046
4070
|
function isAgenticCommand(commandTemplate) {
|
|
4047
4071
|
return commandTemplate.includes("${PROMPT}") && !commandTemplate.includes("--print");
|
|
4048
4072
|
}
|
|
4049
|
-
function executeAgentic(commandTemplate, prompt2, timeoutMs, cwd, signal) {
|
|
4073
|
+
function executeAgentic(commandTemplate, prompt2, timeoutMs, cwd, signal, heartbeat) {
|
|
4050
4074
|
const allVars = { PROMPT: prompt2, CODEBASE_DIR: cwd };
|
|
4051
4075
|
const { command, args } = parseCommandTemplate(commandTemplate, allVars);
|
|
4052
4076
|
return new Promise((resolve2, reject) => {
|
|
@@ -4067,6 +4091,7 @@ function executeAgentic(commandTemplate, prompt2, timeoutMs, cwd, signal) {
|
|
|
4067
4091
|
}, 5e3);
|
|
4068
4092
|
}
|
|
4069
4093
|
}, timeoutMs);
|
|
4094
|
+
const stopHeartbeat = startHeartbeatTimer(heartbeat, () => settled);
|
|
4070
4095
|
let onAbort;
|
|
4071
4096
|
if (signal) {
|
|
4072
4097
|
onAbort = () => {
|
|
@@ -4074,16 +4099,19 @@ function executeAgentic(commandTemplate, prompt2, timeoutMs, cwd, signal) {
|
|
|
4074
4099
|
};
|
|
4075
4100
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
4076
4101
|
}
|
|
4077
|
-
|
|
4102
|
+
function agenticCleanup() {
|
|
4078
4103
|
clearTimeout(timer);
|
|
4104
|
+
stopHeartbeat();
|
|
4079
4105
|
if (onAbort && signal) signal.removeEventListener("abort", onAbort);
|
|
4106
|
+
}
|
|
4107
|
+
child.on("error", (err) => {
|
|
4108
|
+
agenticCleanup();
|
|
4080
4109
|
if (settled) return;
|
|
4081
4110
|
settled = true;
|
|
4082
4111
|
reject(err);
|
|
4083
4112
|
});
|
|
4084
4113
|
child.on("close", (code, sig) => {
|
|
4085
|
-
|
|
4086
|
-
if (onAbort && signal) signal.removeEventListener("abort", onAbort);
|
|
4114
|
+
agenticCleanup();
|
|
4087
4115
|
if (settled) return;
|
|
4088
4116
|
settled = true;
|
|
4089
4117
|
if (sig === "SIGTERM" || sig === "SIGKILL") {
|
|
@@ -4107,7 +4135,8 @@ async function executeImplement(task, worktreePath, deps, timeoutSeconds, signal
|
|
|
4107
4135
|
prompt2,
|
|
4108
4136
|
effectiveTimeout,
|
|
4109
4137
|
worktreePath,
|
|
4110
|
-
signal
|
|
4138
|
+
signal,
|
|
4139
|
+
deps.heartbeat
|
|
4111
4140
|
);
|
|
4112
4141
|
return {
|
|
4113
4142
|
output: {
|
|
@@ -4126,7 +4155,9 @@ async function executeImplement(task, worktreePath, deps, timeoutSeconds, signal
|
|
|
4126
4155
|
effectiveTimeout,
|
|
4127
4156
|
signal,
|
|
4128
4157
|
void 0,
|
|
4129
|
-
worktreePath
|
|
4158
|
+
worktreePath,
|
|
4159
|
+
void 0,
|
|
4160
|
+
deps.heartbeat
|
|
4130
4161
|
);
|
|
4131
4162
|
const output = parseImplementOutput(result.stdout);
|
|
4132
4163
|
const inputTokens = result.tokensParsed ? 0 : estimateTokens(prompt2);
|
|
@@ -4348,7 +4379,9 @@ async function executeFix(task, diffContent, deps, timeoutSeconds, worktreePath,
|
|
|
4348
4379
|
effectiveTimeout,
|
|
4349
4380
|
signal,
|
|
4350
4381
|
void 0,
|
|
4351
|
-
worktreePath
|
|
4382
|
+
worktreePath,
|
|
4383
|
+
void 0,
|
|
4384
|
+
deps.heartbeat
|
|
4352
4385
|
);
|
|
4353
4386
|
const inputTokens = result.tokensParsed ? 0 : estimateTokens(prompt2);
|
|
4354
4387
|
const detail = result.tokenDetail;
|
|
@@ -5067,6 +5100,49 @@ async function pollLoop(client, agentId, reviewDeps, consumptionDeps, agentInfo,
|
|
|
5067
5100
|
await sleep2(pollIntervalMs, signal);
|
|
5068
5101
|
}
|
|
5069
5102
|
}
|
|
5103
|
+
function isLongRunningRole(role) {
|
|
5104
|
+
return isFixRole(role) || isImplementRole(role) || role === "review" || role === "summary";
|
|
5105
|
+
}
|
|
5106
|
+
async function runWithHeartbeat(heartbeat, work) {
|
|
5107
|
+
let done = false;
|
|
5108
|
+
const stop = startHeartbeatTimer(heartbeat, () => done);
|
|
5109
|
+
try {
|
|
5110
|
+
return await work();
|
|
5111
|
+
} finally {
|
|
5112
|
+
done = true;
|
|
5113
|
+
stop();
|
|
5114
|
+
}
|
|
5115
|
+
}
|
|
5116
|
+
function createHeartbeatControl(client, taskId, agentId, role, logger, intervalMs = DEFAULT_HEARTBEAT_INTERVAL_MS) {
|
|
5117
|
+
let sawNotFound = false;
|
|
5118
|
+
let failureStreakLogged = false;
|
|
5119
|
+
return {
|
|
5120
|
+
intervalMs,
|
|
5121
|
+
callback: async () => {
|
|
5122
|
+
try {
|
|
5123
|
+
await client.post(`/api/tasks/${taskId}/heartbeat`, {
|
|
5124
|
+
agent_id: agentId,
|
|
5125
|
+
role
|
|
5126
|
+
});
|
|
5127
|
+
failureStreakLogged = false;
|
|
5128
|
+
} catch (err) {
|
|
5129
|
+
if (err instanceof HttpError && err.status === 404) {
|
|
5130
|
+
if (!sawNotFound) {
|
|
5131
|
+
sawNotFound = true;
|
|
5132
|
+
logger.log(` (heartbeat endpoint not available \u2014 old server, continuing)`);
|
|
5133
|
+
}
|
|
5134
|
+
return;
|
|
5135
|
+
}
|
|
5136
|
+
if (!failureStreakLogged) {
|
|
5137
|
+
failureStreakLogged = true;
|
|
5138
|
+
logger.logWarn(
|
|
5139
|
+
` ${icons.warn} Heartbeat failed for task ${taskId}: ${err.message} (further failures suppressed until next success)`
|
|
5140
|
+
);
|
|
5141
|
+
}
|
|
5142
|
+
}
|
|
5143
|
+
}
|
|
5144
|
+
};
|
|
5145
|
+
}
|
|
5070
5146
|
async function handleTask(client, agentId, task, reviewDeps, consumptionDeps, agentInfo, logger, agentSession, routerRelay, signal, cleanupTracker, verbose) {
|
|
5071
5147
|
const { task_id, owner, repo, pr_number, diff_url, timeout_seconds, prompt: prompt2, role, base_ref } = task;
|
|
5072
5148
|
const { log, logError, logWarn } = logger;
|
|
@@ -5211,12 +5287,14 @@ async function handleTask(client, agentId, task, reviewDeps, consumptionDeps, ag
|
|
|
5211
5287
|
log(" (suspicious prompt report not sent \u2014 endpoint not available)");
|
|
5212
5288
|
}
|
|
5213
5289
|
}
|
|
5290
|
+
const heartbeat = isLongRunningRole(role) ? createHeartbeatControl(client, task_id, agentId, role, logger) : void 0;
|
|
5214
5291
|
try {
|
|
5215
5292
|
if (isImplementRole(role)) {
|
|
5216
5293
|
const codebaseDir = reviewDeps.codebaseDir || path9.join(CONFIG_DIR, "repos");
|
|
5217
5294
|
const implementDeps = {
|
|
5218
5295
|
commandTemplate: reviewDeps.commandTemplate,
|
|
5219
|
-
codebaseDir
|
|
5296
|
+
codebaseDir,
|
|
5297
|
+
heartbeat
|
|
5220
5298
|
};
|
|
5221
5299
|
const implementResult = await executeImplementTask(
|
|
5222
5300
|
client,
|
|
@@ -5250,7 +5328,8 @@ async function handleTask(client, agentId, task, reviewDeps, consumptionDeps, ag
|
|
|
5250
5328
|
throw new Error("Fix task requires a codebase worktree but checkout failed");
|
|
5251
5329
|
}
|
|
5252
5330
|
const fixDeps = {
|
|
5253
|
-
commandTemplate: reviewDeps.commandTemplate
|
|
5331
|
+
commandTemplate: reviewDeps.commandTemplate,
|
|
5332
|
+
heartbeat
|
|
5254
5333
|
};
|
|
5255
5334
|
const fixResult = await executeFixTask(
|
|
5256
5335
|
client,
|
|
@@ -5364,6 +5443,7 @@ async function handleTask(client, agentId, task, reviewDeps, consumptionDeps, ag
|
|
|
5364
5443
|
{ execGh: defaultExecGh }
|
|
5365
5444
|
);
|
|
5366
5445
|
} else if (role === "summary" && "reviews" in claimResponse && claimResponse.reviews) {
|
|
5446
|
+
const summaryDeps = { ...taskReviewDeps, heartbeat };
|
|
5367
5447
|
await executeSummaryTask(
|
|
5368
5448
|
client,
|
|
5369
5449
|
agentId,
|
|
@@ -5375,7 +5455,7 @@ async function handleTask(client, agentId, task, reviewDeps, consumptionDeps, ag
|
|
|
5375
5455
|
prompt2,
|
|
5376
5456
|
timeout_seconds,
|
|
5377
5457
|
claimResponse.reviews,
|
|
5378
|
-
|
|
5458
|
+
summaryDeps,
|
|
5379
5459
|
consumptionDeps,
|
|
5380
5460
|
logger,
|
|
5381
5461
|
agentInfo,
|
|
@@ -5385,6 +5465,7 @@ async function handleTask(client, agentId, task, reviewDeps, consumptionDeps, ag
|
|
|
5385
5465
|
verbose
|
|
5386
5466
|
);
|
|
5387
5467
|
} else {
|
|
5468
|
+
const reviewDepsWithHeartbeat = { ...taskReviewDeps, heartbeat };
|
|
5388
5469
|
await executeReviewTask(
|
|
5389
5470
|
client,
|
|
5390
5471
|
agentId,
|
|
@@ -5395,7 +5476,7 @@ async function handleTask(client, agentId, task, reviewDeps, consumptionDeps, ag
|
|
|
5395
5476
|
diffContent,
|
|
5396
5477
|
prompt2,
|
|
5397
5478
|
timeout_seconds,
|
|
5398
|
-
|
|
5479
|
+
reviewDepsWithHeartbeat,
|
|
5399
5480
|
consumptionDeps,
|
|
5400
5481
|
logger,
|
|
5401
5482
|
agentInfo,
|
|
@@ -5488,11 +5569,9 @@ async function executeReviewTask(client, agentId, taskId, owner, repo, prNumber,
|
|
|
5488
5569
|
diffContent,
|
|
5489
5570
|
contextBlock
|
|
5490
5571
|
});
|
|
5491
|
-
const response = await
|
|
5492
|
-
|
|
5493
|
-
taskId,
|
|
5494
|
-
fullPrompt,
|
|
5495
|
-
timeoutSeconds
|
|
5572
|
+
const response = await runWithHeartbeat(
|
|
5573
|
+
reviewDeps.heartbeat,
|
|
5574
|
+
() => routerRelay.sendPrompt("review_request", taskId, fullPrompt, timeoutSeconds)
|
|
5496
5575
|
);
|
|
5497
5576
|
const parsed = routerRelay.parseReviewResponse(response);
|
|
5498
5577
|
reviewText = parsed.review;
|
|
@@ -5587,11 +5666,9 @@ async function executeSummaryTask(client, agentId, taskId, owner, repo, prNumber
|
|
|
5587
5666
|
diffContent,
|
|
5588
5667
|
contextBlock
|
|
5589
5668
|
});
|
|
5590
|
-
const response = await
|
|
5591
|
-
|
|
5592
|
-
taskId,
|
|
5593
|
-
fullPrompt,
|
|
5594
|
-
timeoutSeconds
|
|
5669
|
+
const response = await runWithHeartbeat(
|
|
5670
|
+
reviewDeps.heartbeat,
|
|
5671
|
+
() => routerRelay.sendPrompt("review_request", taskId, fullPrompt, timeoutSeconds)
|
|
5595
5672
|
);
|
|
5596
5673
|
const parsed = routerRelay.parseReviewResponse(response);
|
|
5597
5674
|
reviewText = parsed.review;
|
|
@@ -5690,11 +5767,9 @@ async function executeSummaryTask(client, agentId, taskId, owner, repo, prNumber
|
|
|
5690
5767
|
diffContent,
|
|
5691
5768
|
contextBlock
|
|
5692
5769
|
});
|
|
5693
|
-
const response = await
|
|
5694
|
-
|
|
5695
|
-
taskId,
|
|
5696
|
-
fullPrompt,
|
|
5697
|
-
timeoutSeconds
|
|
5770
|
+
const response = await runWithHeartbeat(
|
|
5771
|
+
reviewDeps.heartbeat,
|
|
5772
|
+
() => routerRelay.sendPrompt("summary_request", taskId, fullPrompt, timeoutSeconds)
|
|
5698
5773
|
);
|
|
5699
5774
|
const parsed = extractVerdict(response);
|
|
5700
5775
|
summaryText = parsed.review;
|
|
@@ -5803,7 +5878,7 @@ function sleep2(ms, signal) {
|
|
|
5803
5878
|
async function startAgent(agentId, platformUrl, agentInfo, reviewDeps, consumptionDeps, options) {
|
|
5804
5879
|
const client = new ApiClient(platformUrl, {
|
|
5805
5880
|
authToken: options?.authToken,
|
|
5806
|
-
cliVersion: "0.25.
|
|
5881
|
+
cliVersion: "0.25.2",
|
|
5807
5882
|
versionOverride: options?.versionOverride,
|
|
5808
5883
|
onTokenRefresh: options?.onTokenRefresh
|
|
5809
5884
|
});
|
|
@@ -6168,7 +6243,7 @@ async function startBatchAgents(config, agents, pollIntervalMs, oauthToken, opti
|
|
|
6168
6243
|
const { versionOverride, verbose, instancesOverride, agentOwner, userOrgs } = options;
|
|
6169
6244
|
const client = new ApiClient(config.platformUrl, {
|
|
6170
6245
|
authToken: oauthToken,
|
|
6171
|
-
cliVersion: "0.25.
|
|
6246
|
+
cliVersion: "0.25.2",
|
|
6172
6247
|
versionOverride,
|
|
6173
6248
|
onTokenRefresh: () => getValidToken(config.platformUrl, { configPath: config.authFile })
|
|
6174
6249
|
});
|
|
@@ -6519,7 +6594,7 @@ agentCommand.command("start").description("Start agents in polling mode").option
|
|
|
6519
6594
|
}
|
|
6520
6595
|
config = loadConfig();
|
|
6521
6596
|
}
|
|
6522
|
-
console.log(formatVersionBanner("0.25.
|
|
6597
|
+
console.log(formatVersionBanner("0.25.2", "c374877"));
|
|
6523
6598
|
if (config.agents && config.agents.length > 0) {
|
|
6524
6599
|
const toolEntries = config.agents.map((a) => ({
|
|
6525
6600
|
tool: a.tool,
|
|
@@ -7341,7 +7416,7 @@ var statusCommand = new Command4("status").description("Show agent config, conne
|
|
|
7341
7416
|
});
|
|
7342
7417
|
|
|
7343
7418
|
// src/index.ts
|
|
7344
|
-
var program = new Command5().name("opencara").description("OpenCara \u2014 distributed AI code review agent").version(`${"0.25.
|
|
7419
|
+
var program = new Command5().name("opencara").description("OpenCara \u2014 distributed AI code review agent").version(`${"0.25.2"} (${"c374877"})`);
|
|
7345
7420
|
program.addCommand(agentCommand);
|
|
7346
7421
|
program.addCommand(authCommand());
|
|
7347
7422
|
program.addCommand(dedupCommand());
|