opencara 0.23.6 → 0.23.8
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 +101 -39
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -714,6 +714,20 @@ function parseAgents(data) {
|
|
|
714
714
|
agent.maxTasksPerDay = v;
|
|
715
715
|
}
|
|
716
716
|
}
|
|
717
|
+
if (typeof obj.liveness_timeout === "number") {
|
|
718
|
+
if (obj.liveness_timeout === 0) {
|
|
719
|
+
agent.livenessTimeout = 0;
|
|
720
|
+
} else {
|
|
721
|
+
const v = parsePositiveInt(obj.liveness_timeout);
|
|
722
|
+
if (v === null) {
|
|
723
|
+
console.warn(
|
|
724
|
+
`\u26A0 Config warning: agents[${i}].liveness_timeout must be a non-negative integer (seconds), got ${obj.liveness_timeout}. Value ignored.`
|
|
725
|
+
);
|
|
726
|
+
} else {
|
|
727
|
+
agent.livenessTimeout = v;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
717
731
|
const repoConfig = parseRepoConfig(obj, i);
|
|
718
732
|
if (repoConfig) agent.repos = repoConfig;
|
|
719
733
|
const synthesizeRepoConfig = parseRepoConfig(obj, i, "synthesize_repos");
|
|
@@ -1750,6 +1764,7 @@ var ToolTimeoutError = class extends Error {
|
|
|
1750
1764
|
};
|
|
1751
1765
|
var SIGKILL_GRACE_MS = 5e3;
|
|
1752
1766
|
var MIN_PARTIAL_RESULT_LENGTH = 50;
|
|
1767
|
+
var STDOUT_LIVENESS_TIMEOUT_MS = 3e5;
|
|
1753
1768
|
var MAX_STDERR_LENGTH = 1e3;
|
|
1754
1769
|
function validateCommandBinary(commandTemplate) {
|
|
1755
1770
|
const { command } = parseCommandTemplate(commandTemplate);
|
|
@@ -1846,7 +1861,7 @@ function parseTokenUsage(stdout, stderr) {
|
|
|
1846
1861
|
const estimated = estimateTokens(stdout);
|
|
1847
1862
|
return { tokens: estimated, parsed: false, input: 0, output: estimated };
|
|
1848
1863
|
}
|
|
1849
|
-
function executeTool(commandTemplate, prompt2, timeoutMs, signal, vars, cwd) {
|
|
1864
|
+
function executeTool(commandTemplate, prompt2, timeoutMs, signal, vars, cwd, livenessTimeoutMs) {
|
|
1850
1865
|
const promptViaArg = commandTemplate.includes("${PROMPT}");
|
|
1851
1866
|
const allVars = { ...vars, PROMPT: prompt2 };
|
|
1852
1867
|
if (cwd && !allVars["CODEBASE_DIR"]) {
|
|
@@ -1866,7 +1881,12 @@ function executeTool(commandTemplate, prompt2, timeoutMs, signal, vars, cwd) {
|
|
|
1866
1881
|
let stderr = "";
|
|
1867
1882
|
let settled = false;
|
|
1868
1883
|
let sigkillTimer;
|
|
1884
|
+
let killedByLiveness = false;
|
|
1885
|
+
const effectiveLivenessMs = livenessTimeoutMs === void 0 ? STDOUT_LIVENESS_TIMEOUT_MS : livenessTimeoutMs;
|
|
1886
|
+
let killScheduled = false;
|
|
1869
1887
|
function scheduleKillEscalation() {
|
|
1888
|
+
if (killScheduled) return;
|
|
1889
|
+
killScheduled = true;
|
|
1870
1890
|
child.kill("SIGTERM");
|
|
1871
1891
|
if (sigkillTimer) clearTimeout(sigkillTimer);
|
|
1872
1892
|
sigkillTimer = setTimeout(() => {
|
|
@@ -1876,8 +1896,26 @@ function executeTool(commandTemplate, prompt2, timeoutMs, signal, vars, cwd) {
|
|
|
1876
1896
|
}, SIGKILL_GRACE_MS);
|
|
1877
1897
|
}
|
|
1878
1898
|
const timer = setTimeout(scheduleKillEscalation, timeoutMs);
|
|
1899
|
+
let livenessTimer;
|
|
1900
|
+
if (effectiveLivenessMs > 0) {
|
|
1901
|
+
livenessTimer = setTimeout(() => {
|
|
1902
|
+
if (!settled) {
|
|
1903
|
+
killedByLiveness = true;
|
|
1904
|
+
scheduleKillEscalation();
|
|
1905
|
+
}
|
|
1906
|
+
}, effectiveLivenessMs);
|
|
1907
|
+
}
|
|
1879
1908
|
child.stdout?.on("data", (chunk) => {
|
|
1880
1909
|
stdout += chunk.toString();
|
|
1910
|
+
if (livenessTimer) {
|
|
1911
|
+
clearTimeout(livenessTimer);
|
|
1912
|
+
livenessTimer = setTimeout(() => {
|
|
1913
|
+
if (!settled) {
|
|
1914
|
+
killedByLiveness = true;
|
|
1915
|
+
scheduleKillEscalation();
|
|
1916
|
+
}
|
|
1917
|
+
}, effectiveLivenessMs);
|
|
1918
|
+
}
|
|
1881
1919
|
});
|
|
1882
1920
|
child.stderr?.on("data", (chunk) => {
|
|
1883
1921
|
stderr += chunk.toString();
|
|
@@ -1893,6 +1931,7 @@ function executeTool(commandTemplate, prompt2, timeoutMs, signal, vars, cwd) {
|
|
|
1893
1931
|
}
|
|
1894
1932
|
function cleanup() {
|
|
1895
1933
|
clearTimeout(timer);
|
|
1934
|
+
if (livenessTimer) clearTimeout(livenessTimer);
|
|
1896
1935
|
if (sigkillTimer) clearTimeout(sigkillTimer);
|
|
1897
1936
|
if (onAbort && signal) {
|
|
1898
1937
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -1917,11 +1956,19 @@ function executeTool(commandTemplate, prompt2, timeoutMs, signal, vars, cwd) {
|
|
|
1917
1956
|
return;
|
|
1918
1957
|
}
|
|
1919
1958
|
if (sig === "SIGTERM" || sig === "SIGKILL") {
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1959
|
+
if (killedByLiveness) {
|
|
1960
|
+
reject(
|
|
1961
|
+
new ToolTimeoutError(
|
|
1962
|
+
`Tool "${command}" killed: no stdout for ${Math.round(effectiveLivenessMs / 1e3)}s (process may be stuck)`
|
|
1963
|
+
)
|
|
1964
|
+
);
|
|
1965
|
+
} else {
|
|
1966
|
+
reject(
|
|
1967
|
+
new ToolTimeoutError(
|
|
1968
|
+
`Tool "${command}" timed out after ${Math.round(timeoutMs / 1e3)}s`
|
|
1969
|
+
)
|
|
1970
|
+
);
|
|
1971
|
+
}
|
|
1925
1972
|
return;
|
|
1926
1973
|
}
|
|
1927
1974
|
if (code !== 0) {
|
|
@@ -2500,7 +2547,8 @@ ${userMessage}`;
|
|
|
2500
2547
|
effectiveTimeout,
|
|
2501
2548
|
abortController.signal,
|
|
2502
2549
|
void 0,
|
|
2503
|
-
deps.codebaseDir ?? void 0
|
|
2550
|
+
deps.codebaseDir ?? void 0,
|
|
2551
|
+
deps.livenessTimeoutMs
|
|
2504
2552
|
);
|
|
2505
2553
|
const { verdict, review } = extractVerdict(result.stdout);
|
|
2506
2554
|
const inputTokens = result.tokensParsed ? 0 : estimateTokens(fullPrompt);
|
|
@@ -2615,7 +2663,8 @@ ${userMessage}`;
|
|
|
2615
2663
|
effectiveTimeout,
|
|
2616
2664
|
abortController.signal,
|
|
2617
2665
|
void 0,
|
|
2618
|
-
deps.codebaseDir ?? void 0
|
|
2666
|
+
deps.codebaseDir ?? void 0,
|
|
2667
|
+
deps.livenessTimeoutMs
|
|
2619
2668
|
);
|
|
2620
2669
|
const { verdict, review } = extractVerdict(result.stdout);
|
|
2621
2670
|
const flaggedReviews = extractFlaggedReviews(result.stdout);
|
|
@@ -5663,7 +5712,7 @@ function sleep2(ms, signal) {
|
|
|
5663
5712
|
async function startAgent(agentId, platformUrl, agentInfo, reviewDeps, consumptionDeps, options) {
|
|
5664
5713
|
const client = new ApiClient(platformUrl, {
|
|
5665
5714
|
authToken: options?.authToken,
|
|
5666
|
-
cliVersion: "0.23.
|
|
5715
|
+
cliVersion: "0.23.8",
|
|
5667
5716
|
versionOverride: options?.versionOverride,
|
|
5668
5717
|
onTokenRefresh: options?.onTokenRefresh
|
|
5669
5718
|
});
|
|
@@ -5770,6 +5819,8 @@ async function batchPollLoop(client, agentStates, options) {
|
|
|
5770
5819
|
let consecutiveAuthErrors = 0;
|
|
5771
5820
|
let consecutiveErrors = 0;
|
|
5772
5821
|
let pollCycleCount = 0;
|
|
5822
|
+
const busyAgents = /* @__PURE__ */ new Set();
|
|
5823
|
+
const inflightPromises = /* @__PURE__ */ new Set();
|
|
5773
5824
|
while (!signal?.aborted) {
|
|
5774
5825
|
if (accessibleRepos && githubToken && recheckInterval > 0 && pollCycleCount > 0 && pollCycleCount % recheckInterval === 0) {
|
|
5775
5826
|
const allRepos = extractRepoUrls(
|
|
@@ -5826,13 +5877,17 @@ async function batchPollLoop(client, agentStates, options) {
|
|
|
5826
5877
|
break;
|
|
5827
5878
|
}
|
|
5828
5879
|
try {
|
|
5829
|
-
const
|
|
5880
|
+
const availableStates = agentStates.filter((s) => !busyAgents.has(s));
|
|
5881
|
+
if (availableStates.length === 0) {
|
|
5882
|
+
await sleep2(pollIntervalMs, signal);
|
|
5883
|
+
continue;
|
|
5884
|
+
}
|
|
5885
|
+
const descriptors = availableStates.map((s) => s.descriptor);
|
|
5830
5886
|
const request = buildBatchPollRequest(descriptors);
|
|
5831
5887
|
const response = await client.post("/api/tasks/poll/batch", request);
|
|
5832
5888
|
consecutiveAuthErrors = 0;
|
|
5833
5889
|
consecutiveErrors = 0;
|
|
5834
|
-
const
|
|
5835
|
-
for (const state of agentStates) {
|
|
5890
|
+
for (const state of availableStates) {
|
|
5836
5891
|
const agentName = state.descriptor.name;
|
|
5837
5892
|
const pollResponse = response.assignments[agentName];
|
|
5838
5893
|
if (!pollResponse || pollResponse.tasks.length === 0) continue;
|
|
@@ -5846,8 +5901,9 @@ async function batchPollLoop(client, agentStates, options) {
|
|
|
5846
5901
|
);
|
|
5847
5902
|
const task = eligible[0];
|
|
5848
5903
|
if (!task) continue;
|
|
5849
|
-
|
|
5850
|
-
|
|
5904
|
+
busyAgents.add(state);
|
|
5905
|
+
const p = (async () => {
|
|
5906
|
+
try {
|
|
5851
5907
|
const result = await handleTask(
|
|
5852
5908
|
client,
|
|
5853
5909
|
state.descriptor.agentId,
|
|
@@ -5876,28 +5932,27 @@ async function batchPollLoop(client, agentStates, options) {
|
|
|
5876
5932
|
);
|
|
5877
5933
|
}
|
|
5878
5934
|
}
|
|
5879
|
-
}
|
|
5880
|
-
|
|
5881
|
-
}
|
|
5882
|
-
if (handlePromises.length > 0) {
|
|
5883
|
-
const results = await Promise.allSettled(handlePromises);
|
|
5884
|
-
for (const r of results) {
|
|
5885
|
-
if (r.status === "rejected") {
|
|
5886
|
-
logError(`${icons.error} Task handler failed: ${r.reason}`);
|
|
5935
|
+
} catch (err) {
|
|
5936
|
+
logError(`${icons.error} Task handler failed: ${err.message}`);
|
|
5887
5937
|
consecutiveErrors++;
|
|
5938
|
+
} finally {
|
|
5939
|
+
busyAgents.delete(state);
|
|
5940
|
+
if (state.cleanupTracker) {
|
|
5941
|
+
try {
|
|
5942
|
+
const swept = await state.cleanupTracker.sweep(cleanupWorktree);
|
|
5943
|
+
if (swept > 0) {
|
|
5944
|
+
state.logger.log(
|
|
5945
|
+
`${icons.info} Cleaned up ${swept} stale codebase director${swept === 1 ? "y" : "ies"}`
|
|
5946
|
+
);
|
|
5947
|
+
}
|
|
5948
|
+
} catch {
|
|
5949
|
+
}
|
|
5950
|
+
}
|
|
5888
5951
|
}
|
|
5889
|
-
}
|
|
5952
|
+
})();
|
|
5953
|
+
inflightPromises.add(p);
|
|
5954
|
+
void p.finally(() => inflightPromises.delete(p));
|
|
5890
5955
|
}
|
|
5891
|
-
await Promise.allSettled(
|
|
5892
|
-
agentStates.filter((state) => state.cleanupTracker).map(async (state) => {
|
|
5893
|
-
const swept = await state.cleanupTracker.sweep(cleanupWorktree);
|
|
5894
|
-
if (swept > 0) {
|
|
5895
|
-
state.logger.log(
|
|
5896
|
-
`${icons.info} Cleaned up ${swept} stale codebase director${swept === 1 ? "y" : "ies"}`
|
|
5897
|
-
);
|
|
5898
|
-
}
|
|
5899
|
-
})
|
|
5900
|
-
);
|
|
5901
5956
|
} catch (err) {
|
|
5902
5957
|
if (signal?.aborted) break;
|
|
5903
5958
|
if (err instanceof UpgradeRequiredError) {
|
|
@@ -5944,12 +5999,16 @@ async function batchPollLoop(client, agentStates, options) {
|
|
|
5944
5999
|
}
|
|
5945
6000
|
await sleep2(pollIntervalMs, signal);
|
|
5946
6001
|
}
|
|
6002
|
+
if (inflightPromises.size > 0) {
|
|
6003
|
+
log(`${icons.info} Waiting for ${inflightPromises.size} in-flight task(s) to complete...`);
|
|
6004
|
+
await Promise.allSettled([...inflightPromises]);
|
|
6005
|
+
}
|
|
5947
6006
|
}
|
|
5948
6007
|
async function startBatchAgents(config, agents, pollIntervalMs, oauthToken, options) {
|
|
5949
6008
|
const { versionOverride, verbose, instancesOverride, agentOwner, userOrgs } = options;
|
|
5950
6009
|
const client = new ApiClient(config.platformUrl, {
|
|
5951
6010
|
authToken: oauthToken,
|
|
5952
|
-
cliVersion: "0.23.
|
|
6011
|
+
cliVersion: "0.23.8",
|
|
5953
6012
|
versionOverride,
|
|
5954
6013
|
onTokenRefresh: () => getValidToken(config.platformUrl, { configPath: config.authFile })
|
|
5955
6014
|
});
|
|
@@ -5995,7 +6054,8 @@ async function startBatchAgents(config, agents, pollIntervalMs, oauthToken, opti
|
|
|
5995
6054
|
commandTemplate,
|
|
5996
6055
|
maxDiffSizeKb: config.maxDiffSizeKb,
|
|
5997
6056
|
maxRepoSizeMb: config.maxRepoSizeMb,
|
|
5998
|
-
codebaseDir
|
|
6057
|
+
codebaseDir,
|
|
6058
|
+
livenessTimeoutMs: agentConfig.livenessTimeout != null ? agentConfig.livenessTimeout * 1e3 : void 0
|
|
5999
6059
|
};
|
|
6000
6060
|
const session = createSessionTracker();
|
|
6001
6061
|
const usageTracker = new UsageTracker();
|
|
@@ -6151,7 +6211,8 @@ async function startAgentRouter() {
|
|
|
6151
6211
|
commandTemplate: commandTemplate ?? "",
|
|
6152
6212
|
maxDiffSizeKb: config.maxDiffSizeKb,
|
|
6153
6213
|
maxRepoSizeMb: config.maxRepoSizeMb,
|
|
6154
|
-
codebaseDir
|
|
6214
|
+
codebaseDir,
|
|
6215
|
+
livenessTimeoutMs: agentConfig?.livenessTimeout != null ? agentConfig.livenessTimeout * 1e3 : void 0
|
|
6155
6216
|
};
|
|
6156
6217
|
const session = createSessionTracker();
|
|
6157
6218
|
const usageTracker = new UsageTracker();
|
|
@@ -6218,7 +6279,8 @@ function startAgentByIndex(config, agentIndex, pollIntervalMs, oauthToken, versi
|
|
|
6218
6279
|
commandTemplate,
|
|
6219
6280
|
maxDiffSizeKb: config.maxDiffSizeKb,
|
|
6220
6281
|
maxRepoSizeMb: config.maxRepoSizeMb,
|
|
6221
|
-
codebaseDir
|
|
6282
|
+
codebaseDir,
|
|
6283
|
+
livenessTimeoutMs: agentConfig?.livenessTimeout != null ? agentConfig.livenessTimeout * 1e3 : void 0
|
|
6222
6284
|
};
|
|
6223
6285
|
const model = agentConfig?.model ?? "unknown";
|
|
6224
6286
|
const tool = agentConfig?.tool ?? "unknown";
|
|
@@ -6292,7 +6354,7 @@ agentCommand.command("start").description("Start agents in polling mode").option
|
|
|
6292
6354
|
}
|
|
6293
6355
|
config = loadConfig();
|
|
6294
6356
|
}
|
|
6295
|
-
console.log(formatVersionBanner("0.23.
|
|
6357
|
+
console.log(formatVersionBanner("0.23.8", "4d448ed"));
|
|
6296
6358
|
if (config.agents && config.agents.length > 0) {
|
|
6297
6359
|
const toolEntries = config.agents.map((a) => ({
|
|
6298
6360
|
tool: a.tool,
|
|
@@ -7115,7 +7177,7 @@ var statusCommand = new Command4("status").description("Show agent config, conne
|
|
|
7115
7177
|
});
|
|
7116
7178
|
|
|
7117
7179
|
// src/index.ts
|
|
7118
|
-
var program = new Command5().name("opencara").description("OpenCara \u2014 distributed AI code review agent").version(`${"0.23.
|
|
7180
|
+
var program = new Command5().name("opencara").description("OpenCara \u2014 distributed AI code review agent").version(`${"0.23.8"} (${"4d448ed"})`);
|
|
7119
7181
|
program.addCommand(agentCommand);
|
|
7120
7182
|
program.addCommand(authCommand());
|
|
7121
7183
|
program.addCommand(dedupCommand());
|