opencara 0.24.2 → 0.25.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 +92 -31
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1790,7 +1790,7 @@ function fetchPRRef(bareRepoPath, prNumber, ghAvailable) {
|
|
|
1790
1790
|
bareRepoPath
|
|
1791
1791
|
);
|
|
1792
1792
|
}
|
|
1793
|
-
function addWorktree(bareRepoPath, worktreeKey) {
|
|
1793
|
+
function addWorktree(bareRepoPath, worktreeKey, targetRef) {
|
|
1794
1794
|
validatePathSegment(worktreeKey, "worktreeKey");
|
|
1795
1795
|
const repoName = path4.basename(bareRepoPath, ".git");
|
|
1796
1796
|
const worktreeBase = path4.join(path4.dirname(bareRepoPath), `${repoName}-worktrees`);
|
|
@@ -1799,7 +1799,7 @@ function addWorktree(bareRepoPath, worktreeKey) {
|
|
|
1799
1799
|
return worktreePath;
|
|
1800
1800
|
}
|
|
1801
1801
|
fs4.mkdirSync(worktreeBase, { recursive: true });
|
|
1802
|
-
gitExec("git", ["worktree", "add", "--detach", worktreePath,
|
|
1802
|
+
gitExec("git", ["worktree", "add", "--detach", worktreePath, targetRef], bareRepoPath);
|
|
1803
1803
|
return worktreePath;
|
|
1804
1804
|
}
|
|
1805
1805
|
function removeWorktree(bareRepoPath, worktreePath) {
|
|
@@ -1819,6 +1819,9 @@ function repoKeyFromBarePath(bareRepoPath) {
|
|
|
1819
1819
|
const owner = path4.basename(path4.dirname(bareRepoPath));
|
|
1820
1820
|
return `${owner}/${repoName}`;
|
|
1821
1821
|
}
|
|
1822
|
+
function resolveFetchedPrCommit(bareRepoPath) {
|
|
1823
|
+
return gitExec("git", ["rev-parse", "--verify", "FETCH_HEAD"], bareRepoPath).trim();
|
|
1824
|
+
}
|
|
1822
1825
|
async function checkoutWorktree(owner, repo, prNumber, baseDir, _taskId, sparseOptions) {
|
|
1823
1826
|
validatePathSegment(owner, "owner");
|
|
1824
1827
|
validatePathSegment(repo, "repo");
|
|
@@ -1829,8 +1832,9 @@ async function checkoutWorktree(owner, repo, prNumber, baseDir, _taskId, sparseO
|
|
|
1829
1832
|
return withRepoLock(repoKey, () => {
|
|
1830
1833
|
const { bareRepoPath, cloned } = ensureBareClone(owner, repo, baseDir, ghAvailable);
|
|
1831
1834
|
fetchPRRef(bareRepoPath, prNumber, ghAvailable);
|
|
1832
|
-
const
|
|
1833
|
-
|
|
1835
|
+
const prHeadCommit = resolveFetchedPrCommit(bareRepoPath);
|
|
1836
|
+
const worktreePath = addWorktree(bareRepoPath, wtKey, prHeadCommit);
|
|
1837
|
+
gitExec("git", ["checkout", "--detach", "--force", prHeadCommit], worktreePath);
|
|
1834
1838
|
if (useSparse) {
|
|
1835
1839
|
configureSparseCheckout(worktreePath, sparseOptions.diffPaths);
|
|
1836
1840
|
}
|
|
@@ -1876,18 +1880,71 @@ function gitExec(command, args, cwd, opts) {
|
|
|
1876
1880
|
throw new Error(sanitizeTokens(message));
|
|
1877
1881
|
}
|
|
1878
1882
|
}
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
+
var DEFAULT_BRANCH_FALLBACKS = ["main", "master"];
|
|
1884
|
+
function isValidBranchName(name) {
|
|
1885
|
+
if (!name) return false;
|
|
1886
|
+
if (name.startsWith("-")) return false;
|
|
1887
|
+
if (name.includes("..")) return false;
|
|
1888
|
+
return /^[A-Za-z0-9_./-]+$/.test(name);
|
|
1889
|
+
}
|
|
1890
|
+
function fetchBranch(bareRepoPath, branch, ghAvailable) {
|
|
1883
1891
|
const credArgs = ghAvailable ? ["-c", `credential.helper=${GH_CREDENTIAL_HELPER}`] : [];
|
|
1884
1892
|
gitExec(
|
|
1885
1893
|
"git",
|
|
1886
|
-
[...credArgs, "fetch", "--force", "origin", `${
|
|
1894
|
+
[...credArgs, "fetch", "--force", "origin", `${branch}:refs/remotes/origin/${branch}`],
|
|
1887
1895
|
bareRepoPath
|
|
1888
1896
|
);
|
|
1897
|
+
}
|
|
1898
|
+
function isRemoteRefMissingError(err) {
|
|
1899
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1900
|
+
return /couldn't find remote ref/i.test(msg) || /couldnt find remote ref/i.test(msg) || /no such ref/i.test(msg) || /remote ref.*not found/i.test(msg) || /unknown revision or path not in the working tree/i.test(msg);
|
|
1901
|
+
}
|
|
1902
|
+
function deriveDefaultBranch(bareRepoPath, ghAvailable) {
|
|
1903
|
+
try {
|
|
1904
|
+
const out = gitExec("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], bareRepoPath).trim();
|
|
1905
|
+
const prefix = "refs/remotes/origin/";
|
|
1906
|
+
if (out.startsWith(prefix)) {
|
|
1907
|
+
const branch = out.slice(prefix.length);
|
|
1908
|
+
if (isValidBranchName(branch)) {
|
|
1909
|
+
try {
|
|
1910
|
+
fetchBranch(bareRepoPath, branch, ghAvailable);
|
|
1911
|
+
return branch;
|
|
1912
|
+
} catch {
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
} catch {
|
|
1917
|
+
}
|
|
1918
|
+
for (const candidate of DEFAULT_BRANCH_FALLBACKS) {
|
|
1919
|
+
try {
|
|
1920
|
+
fetchBranch(bareRepoPath, candidate, ghAvailable);
|
|
1921
|
+
return candidate;
|
|
1922
|
+
} catch {
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
throw new Error("Cannot derive default branch: origin/HEAD, main, and master all failed");
|
|
1926
|
+
}
|
|
1927
|
+
function diffFromWorktree(bareRepoPath, worktreePath, baseRef, ghAvailable, maxDiffBytes = 128 * 1024 * 1024) {
|
|
1928
|
+
let resolvedBaseRef;
|
|
1929
|
+
if (baseRef) {
|
|
1930
|
+
if (!isValidBranchName(baseRef)) {
|
|
1931
|
+
throw new Error(`Invalid base ref: ${baseRef}`);
|
|
1932
|
+
}
|
|
1933
|
+
try {
|
|
1934
|
+
fetchBranch(bareRepoPath, baseRef, ghAvailable);
|
|
1935
|
+
resolvedBaseRef = baseRef;
|
|
1936
|
+
} catch (err) {
|
|
1937
|
+
if (!isRemoteRefMissingError(err)) {
|
|
1938
|
+
throw err;
|
|
1939
|
+
}
|
|
1940
|
+
resolvedBaseRef = void 0;
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
if (!resolvedBaseRef) {
|
|
1944
|
+
resolvedBaseRef = deriveDefaultBranch(bareRepoPath, ghAvailable);
|
|
1945
|
+
}
|
|
1889
1946
|
try {
|
|
1890
|
-
return gitExec("git", ["diff", `origin/${
|
|
1947
|
+
return gitExec("git", ["diff", `origin/${resolvedBaseRef}...HEAD`], worktreePath, {
|
|
1891
1948
|
maxBuffer: maxDiffBytes
|
|
1892
1949
|
});
|
|
1893
1950
|
} catch (err) {
|
|
@@ -4683,6 +4740,12 @@ function registerShutdownHandlers(controller, log, graceMs = SHUTDOWN_GRACE_MS)
|
|
|
4683
4740
|
};
|
|
4684
4741
|
}
|
|
4685
4742
|
var NON_RETRYABLE_STATUSES = /* @__PURE__ */ new Set([401, 403, 404]);
|
|
4743
|
+
function build404Hint(isGhAuthenticated) {
|
|
4744
|
+
if (isGhAuthenticated) {
|
|
4745
|
+
return ". Diff fetch returned 404. Possible causes: (a) PR not found, (b) the installation can't access this repo, or (c) a transient GitHub outage. See any `[fetchDiffViaGh]` warning above for the underlying `gh api` error.";
|
|
4746
|
+
}
|
|
4747
|
+
return ". If this is a private repo, ensure gh CLI is installed and authenticated: gh auth login";
|
|
4748
|
+
}
|
|
4686
4749
|
function toApiDiffUrl(webUrl) {
|
|
4687
4750
|
const match = webUrl.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)(?:\.diff)?$/);
|
|
4688
4751
|
if (!match) return null;
|
|
@@ -4744,7 +4807,7 @@ function computeRoles(agent) {
|
|
|
4744
4807
|
return ["review", "summary", "implement", "fix"];
|
|
4745
4808
|
}
|
|
4746
4809
|
var DIFF_FETCH_TIMEOUT_MS = 6e4;
|
|
4747
|
-
async function fetchDiffHttp(url, headers, signal, maxDiffSizeKb) {
|
|
4810
|
+
async function fetchDiffHttp(url, headers, signal, maxDiffSizeKb, isGhAuthenticatedFn = isGhAvailable) {
|
|
4748
4811
|
const maxBytes = maxDiffSizeKb ? maxDiffSizeKb * 1024 : Infinity;
|
|
4749
4812
|
const controller = new AbortController();
|
|
4750
4813
|
const timer = setTimeout(() => controller.abort(), DIFF_FETCH_TIMEOUT_MS);
|
|
@@ -4765,7 +4828,7 @@ async function fetchDiffHttp(url, headers, signal, maxDiffSizeKb) {
|
|
|
4765
4828
|
clearTimeout(timer);
|
|
4766
4829
|
signal?.removeEventListener("abort", onParentAbort);
|
|
4767
4830
|
if (!response.ok) {
|
|
4768
|
-
const hint = response.status === 404 ?
|
|
4831
|
+
const hint = response.status === 404 ? build404Hint(isGhAuthenticatedFn()) : "";
|
|
4769
4832
|
const msg = `Failed to fetch diff: ${response.status} ${response.statusText}${hint}`;
|
|
4770
4833
|
if (NON_RETRYABLE_STATUSES.has(response.status)) {
|
|
4771
4834
|
throw new NonRetryableError(msg);
|
|
@@ -5050,7 +5113,12 @@ async function handleTask(client, agentId, task, reviewDeps, consumptionDeps, ag
|
|
|
5050
5113
|
);
|
|
5051
5114
|
taskReviewDeps = { ...reviewDeps, codebaseDir: null };
|
|
5052
5115
|
}
|
|
5053
|
-
if (taskCheckoutPath && taskBareRepoPath
|
|
5116
|
+
if (taskCheckoutPath && taskBareRepoPath) {
|
|
5117
|
+
if (!base_ref) {
|
|
5118
|
+
logWarn(
|
|
5119
|
+
` Warning: task ${task_id} has no base_ref \u2014 deriving default branch from worktree`
|
|
5120
|
+
);
|
|
5121
|
+
}
|
|
5054
5122
|
try {
|
|
5055
5123
|
const ghAvailable = isGhAvailable();
|
|
5056
5124
|
const maxDiffBytes = reviewDeps.maxDiffSizeKb ? reviewDeps.maxDiffSizeKb * 1024 : void 0;
|
|
@@ -5073,23 +5141,16 @@ async function handleTask(client, agentId, task, reviewDeps, consumptionDeps, ag
|
|
|
5073
5141
|
diffContent = gitDiff;
|
|
5074
5142
|
log(` Diff generated via git (${Math.round(diffContent.length / 1024)}KB)`);
|
|
5075
5143
|
} catch (err) {
|
|
5144
|
+
const message = err.message;
|
|
5076
5145
|
if (err instanceof DiffTooLargeError) {
|
|
5077
|
-
logError(` ${
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
task_id,
|
|
5081
|
-
agentId,
|
|
5082
|
-
`Cannot access diff: ${err.message}`,
|
|
5083
|
-
logger
|
|
5084
|
-
);
|
|
5085
|
-
return { diffFetchFailed: true };
|
|
5146
|
+
logError(` ${message}`);
|
|
5147
|
+
} else {
|
|
5148
|
+
logError(` git diff failed for task ${task_id}: ${message}`);
|
|
5086
5149
|
}
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
);
|
|
5150
|
+
await safeReject(client, task_id, agentId, `Cannot access diff: ${message}`, logger);
|
|
5151
|
+
return { diffFetchFailed: true };
|
|
5090
5152
|
}
|
|
5091
|
-
}
|
|
5092
|
-
if (!diffContent) {
|
|
5153
|
+
} else {
|
|
5093
5154
|
try {
|
|
5094
5155
|
const result = await fetchDiff(diff_url, owner, repo, pr_number, {
|
|
5095
5156
|
githubToken: client.currentToken,
|
|
@@ -5732,7 +5793,7 @@ function sleep2(ms, signal) {
|
|
|
5732
5793
|
async function startAgent(agentId, platformUrl, agentInfo, reviewDeps, consumptionDeps, options) {
|
|
5733
5794
|
const client = new ApiClient(platformUrl, {
|
|
5734
5795
|
authToken: options?.authToken,
|
|
5735
|
-
cliVersion: "0.
|
|
5796
|
+
cliVersion: "0.25.0",
|
|
5736
5797
|
versionOverride: options?.versionOverride,
|
|
5737
5798
|
onTokenRefresh: options?.onTokenRefresh
|
|
5738
5799
|
});
|
|
@@ -6097,7 +6158,7 @@ async function startBatchAgents(config, agents, pollIntervalMs, oauthToken, opti
|
|
|
6097
6158
|
const { versionOverride, verbose, instancesOverride, agentOwner, userOrgs } = options;
|
|
6098
6159
|
const client = new ApiClient(config.platformUrl, {
|
|
6099
6160
|
authToken: oauthToken,
|
|
6100
|
-
cliVersion: "0.
|
|
6161
|
+
cliVersion: "0.25.0",
|
|
6101
6162
|
versionOverride,
|
|
6102
6163
|
onTokenRefresh: () => getValidToken(config.platformUrl, { configPath: config.authFile })
|
|
6103
6164
|
});
|
|
@@ -6446,7 +6507,7 @@ agentCommand.command("start").description("Start agents in polling mode").option
|
|
|
6446
6507
|
}
|
|
6447
6508
|
config = loadConfig();
|
|
6448
6509
|
}
|
|
6449
|
-
console.log(formatVersionBanner("0.
|
|
6510
|
+
console.log(formatVersionBanner("0.25.0", "a984ae2"));
|
|
6450
6511
|
if (config.agents && config.agents.length > 0) {
|
|
6451
6512
|
const toolEntries = config.agents.map((a) => ({
|
|
6452
6513
|
tool: a.tool,
|
|
@@ -7268,7 +7329,7 @@ var statusCommand = new Command4("status").description("Show agent config, conne
|
|
|
7268
7329
|
});
|
|
7269
7330
|
|
|
7270
7331
|
// src/index.ts
|
|
7271
|
-
var program = new Command5().name("opencara").description("OpenCara \u2014 distributed AI code review agent").version(`${"0.
|
|
7332
|
+
var program = new Command5().name("opencara").description("OpenCara \u2014 distributed AI code review agent").version(`${"0.25.0"} (${"a984ae2"})`);
|
|
7272
7333
|
program.addCommand(agentCommand);
|
|
7273
7334
|
program.addCommand(authCommand());
|
|
7274
7335
|
program.addCommand(dedupCommand());
|