opencara 0.23.7 → 0.23.9

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.
Files changed (2) hide show
  1. package/dist/index.js +107 -23
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -562,6 +562,15 @@ function ensureConfigDir() {
562
562
  var DEFAULT_MAX_DIFF_SIZE_KB = 100;
563
563
  var DEFAULT_MAX_CONSECUTIVE_ERRORS = 10;
564
564
  var DEFAULT_MAX_REPO_SIZE_MB = 100;
565
+ var DEFAULT_COMMAND_TEST_TIMEOUT_MS = 1e4;
566
+ function parseDurationMs(value) {
567
+ if (typeof value !== "string") return null;
568
+ const secMatch = value.match(/^(\d+)s$/);
569
+ if (secMatch) return parseInt(secMatch[1], 10) * 1e3;
570
+ const minMatch = value.match(/^(\d+)m$/);
571
+ if (minMatch) return parseInt(minMatch[1], 10) * 6e4;
572
+ return null;
573
+ }
565
574
  var VALID_REPO_MODES = ["public", "private", "whitelist", "blacklist"];
566
575
  var REPO_PATTERN = /^[^/]+\/[^/]+$/;
567
576
  var REPO_MODE_ALIASES = {
@@ -714,6 +723,20 @@ function parseAgents(data) {
714
723
  agent.maxTasksPerDay = v;
715
724
  }
716
725
  }
726
+ if (typeof obj.liveness_timeout === "number") {
727
+ if (obj.liveness_timeout === 0) {
728
+ agent.livenessTimeout = 0;
729
+ } else {
730
+ const v = parsePositiveInt(obj.liveness_timeout);
731
+ if (v === null) {
732
+ console.warn(
733
+ `\u26A0 Config warning: agents[${i}].liveness_timeout must be a non-negative integer (seconds), got ${obj.liveness_timeout}. Value ignored.`
734
+ );
735
+ } else {
736
+ agent.livenessTimeout = v;
737
+ }
738
+ }
739
+ }
717
740
  const repoConfig = parseRepoConfig(obj, i);
718
741
  if (repoConfig) agent.repos = repoConfig;
719
742
  const synthesizeRepoConfig = parseRepoConfig(obj, i, "synthesize_repos");
@@ -783,6 +806,7 @@ function loadConfig() {
783
806
  maxRepoSizeMb: DEFAULT_MAX_REPO_SIZE_MB,
784
807
  codebaseDir: null,
785
808
  codebaseTtl: null,
809
+ commandTestTimeoutMs: DEFAULT_COMMAND_TEST_TIMEOUT_MS,
786
810
  agentCommand: null,
787
811
  agents: null,
788
812
  usageLimits: {
@@ -841,6 +865,23 @@ function loadConfig() {
841
865
  maxRepoSizeMb: overrides.maxRepoSizeMb ?? (typeof data.max_repo_size_mb === "number" ? data.max_repo_size_mb : DEFAULT_MAX_REPO_SIZE_MB),
842
866
  codebaseDir: typeof data.codebase_dir === "string" ? data.codebase_dir : null,
843
867
  codebaseTtl: typeof data.codebase_ttl === "string" ? data.codebase_ttl : null,
868
+ commandTestTimeoutMs: (() => {
869
+ if (data.command_test_timeout === void 0) return DEFAULT_COMMAND_TEST_TIMEOUT_MS;
870
+ const ms = parseDurationMs(data.command_test_timeout);
871
+ if (ms === null) {
872
+ console.warn(
873
+ `\u26A0 Config warning: command_test_timeout must be a duration string like "10s" or "1m", got "${data.command_test_timeout}", using default (${DEFAULT_COMMAND_TEST_TIMEOUT_MS / 1e3}s)`
874
+ );
875
+ return DEFAULT_COMMAND_TEST_TIMEOUT_MS;
876
+ }
877
+ if (ms <= 0) {
878
+ console.warn(
879
+ `\u26A0 Config warning: command_test_timeout must be a positive duration, got "${data.command_test_timeout}", using default (${DEFAULT_COMMAND_TEST_TIMEOUT_MS / 1e3}s)`
880
+ );
881
+ return DEFAULT_COMMAND_TEST_TIMEOUT_MS;
882
+ }
883
+ return ms;
884
+ })(),
844
885
  agentCommand: typeof data.agent_command === "string" ? data.agent_command : null,
845
886
  agents: parseAgents(data),
846
887
  usageLimits: {
@@ -1750,6 +1791,7 @@ var ToolTimeoutError = class extends Error {
1750
1791
  };
1751
1792
  var SIGKILL_GRACE_MS = 5e3;
1752
1793
  var MIN_PARTIAL_RESULT_LENGTH = 50;
1794
+ var STDOUT_LIVENESS_TIMEOUT_MS = 3e5;
1753
1795
  var MAX_STDERR_LENGTH = 1e3;
1754
1796
  function validateCommandBinary(commandTemplate) {
1755
1797
  const { command } = parseCommandTemplate(commandTemplate);
@@ -1846,7 +1888,7 @@ function parseTokenUsage(stdout, stderr) {
1846
1888
  const estimated = estimateTokens(stdout);
1847
1889
  return { tokens: estimated, parsed: false, input: 0, output: estimated };
1848
1890
  }
1849
- function executeTool(commandTemplate, prompt2, timeoutMs, signal, vars, cwd) {
1891
+ function executeTool(commandTemplate, prompt2, timeoutMs, signal, vars, cwd, livenessTimeoutMs) {
1850
1892
  const promptViaArg = commandTemplate.includes("${PROMPT}");
1851
1893
  const allVars = { ...vars, PROMPT: prompt2 };
1852
1894
  if (cwd && !allVars["CODEBASE_DIR"]) {
@@ -1866,7 +1908,12 @@ function executeTool(commandTemplate, prompt2, timeoutMs, signal, vars, cwd) {
1866
1908
  let stderr = "";
1867
1909
  let settled = false;
1868
1910
  let sigkillTimer;
1911
+ let killedByLiveness = false;
1912
+ const effectiveLivenessMs = livenessTimeoutMs === void 0 ? STDOUT_LIVENESS_TIMEOUT_MS : livenessTimeoutMs;
1913
+ let killScheduled = false;
1869
1914
  function scheduleKillEscalation() {
1915
+ if (killScheduled) return;
1916
+ killScheduled = true;
1870
1917
  child.kill("SIGTERM");
1871
1918
  if (sigkillTimer) clearTimeout(sigkillTimer);
1872
1919
  sigkillTimer = setTimeout(() => {
@@ -1876,8 +1923,26 @@ function executeTool(commandTemplate, prompt2, timeoutMs, signal, vars, cwd) {
1876
1923
  }, SIGKILL_GRACE_MS);
1877
1924
  }
1878
1925
  const timer = setTimeout(scheduleKillEscalation, timeoutMs);
1926
+ let livenessTimer;
1927
+ if (effectiveLivenessMs > 0) {
1928
+ livenessTimer = setTimeout(() => {
1929
+ if (!settled) {
1930
+ killedByLiveness = true;
1931
+ scheduleKillEscalation();
1932
+ }
1933
+ }, effectiveLivenessMs);
1934
+ }
1879
1935
  child.stdout?.on("data", (chunk) => {
1880
1936
  stdout += chunk.toString();
1937
+ if (livenessTimer) {
1938
+ clearTimeout(livenessTimer);
1939
+ livenessTimer = setTimeout(() => {
1940
+ if (!settled) {
1941
+ killedByLiveness = true;
1942
+ scheduleKillEscalation();
1943
+ }
1944
+ }, effectiveLivenessMs);
1945
+ }
1881
1946
  });
1882
1947
  child.stderr?.on("data", (chunk) => {
1883
1948
  stderr += chunk.toString();
@@ -1893,6 +1958,7 @@ function executeTool(commandTemplate, prompt2, timeoutMs, signal, vars, cwd) {
1893
1958
  }
1894
1959
  function cleanup() {
1895
1960
  clearTimeout(timer);
1961
+ if (livenessTimer) clearTimeout(livenessTimer);
1896
1962
  if (sigkillTimer) clearTimeout(sigkillTimer);
1897
1963
  if (onAbort && signal) {
1898
1964
  signal.removeEventListener("abort", onAbort);
@@ -1917,11 +1983,19 @@ function executeTool(commandTemplate, prompt2, timeoutMs, signal, vars, cwd) {
1917
1983
  return;
1918
1984
  }
1919
1985
  if (sig === "SIGTERM" || sig === "SIGKILL") {
1920
- reject(
1921
- new ToolTimeoutError(
1922
- `Tool "${command}" timed out after ${Math.round(timeoutMs / 1e3)}s`
1923
- )
1924
- );
1986
+ if (killedByLiveness) {
1987
+ reject(
1988
+ new ToolTimeoutError(
1989
+ `Tool "${command}" killed: no stdout for ${Math.round(effectiveLivenessMs / 1e3)}s (process may be stuck)`
1990
+ )
1991
+ );
1992
+ } else {
1993
+ reject(
1994
+ new ToolTimeoutError(
1995
+ `Tool "${command}" timed out after ${Math.round(timeoutMs / 1e3)}s`
1996
+ )
1997
+ );
1998
+ }
1925
1999
  return;
1926
2000
  }
1927
2001
  if (code !== 0) {
@@ -1968,11 +2042,11 @@ function executeTool(commandTemplate, prompt2, timeoutMs, signal, vars, cwd) {
1968
2042
  });
1969
2043
  }
1970
2044
  var TEST_COMMAND_PROMPT = "Respond with: OK";
1971
- var TEST_COMMAND_TIMEOUT_MS = 1e4;
1972
- async function testCommand(commandTemplate) {
2045
+ var DEFAULT_TEST_COMMAND_TIMEOUT_MS = 1e4;
2046
+ async function testCommand(commandTemplate, timeoutMs = DEFAULT_TEST_COMMAND_TIMEOUT_MS) {
1973
2047
  const start = Date.now();
1974
2048
  try {
1975
- await executeTool(commandTemplate, TEST_COMMAND_PROMPT, TEST_COMMAND_TIMEOUT_MS);
2049
+ await executeTool(commandTemplate, TEST_COMMAND_PROMPT, timeoutMs);
1976
2050
  return { ok: true, elapsedMs: Date.now() - start };
1977
2051
  } catch (err) {
1978
2052
  const elapsed = Date.now() - start;
@@ -1980,7 +2054,7 @@ async function testCommand(commandTemplate) {
1980
2054
  return {
1981
2055
  ok: false,
1982
2056
  elapsedMs: elapsed,
1983
- error: `command timed out after ${TEST_COMMAND_TIMEOUT_MS / 1e3}s`
2057
+ error: `command timed out after ${timeoutMs / 1e3}s`
1984
2058
  };
1985
2059
  }
1986
2060
  const msg = err instanceof Error ? err.message : String(err);
@@ -2500,7 +2574,8 @@ ${userMessage}`;
2500
2574
  effectiveTimeout,
2501
2575
  abortController.signal,
2502
2576
  void 0,
2503
- deps.codebaseDir ?? void 0
2577
+ deps.codebaseDir ?? void 0,
2578
+ deps.livenessTimeoutMs
2504
2579
  );
2505
2580
  const { verdict, review } = extractVerdict(result.stdout);
2506
2581
  const inputTokens = result.tokensParsed ? 0 : estimateTokens(fullPrompt);
@@ -2615,7 +2690,8 @@ ${userMessage}`;
2615
2690
  effectiveTimeout,
2616
2691
  abortController.signal,
2617
2692
  void 0,
2618
- deps.codebaseDir ?? void 0
2693
+ deps.codebaseDir ?? void 0,
2694
+ deps.livenessTimeoutMs
2619
2695
  );
2620
2696
  const { verdict, review } = extractVerdict(result.stdout);
2621
2697
  const flaggedReviews = extractFlaggedReviews(result.stdout);
@@ -5663,7 +5739,7 @@ function sleep2(ms, signal) {
5663
5739
  async function startAgent(agentId, platformUrl, agentInfo, reviewDeps, consumptionDeps, options) {
5664
5740
  const client = new ApiClient(platformUrl, {
5665
5741
  authToken: options?.authToken,
5666
- cliVersion: "0.23.7",
5742
+ cliVersion: "0.23.9",
5667
5743
  versionOverride: options?.versionOverride,
5668
5744
  onTokenRefresh: options?.onTokenRefresh
5669
5745
  });
@@ -5697,7 +5773,7 @@ async function startAgent(agentId, platformUrl, agentInfo, reviewDeps, consumpti
5697
5773
  }
5698
5774
  if (reviewDeps.commandTemplate && !options?.routerRelay) {
5699
5775
  log("Testing command...");
5700
- const result = await testCommand(reviewDeps.commandTemplate);
5776
+ const result = await testCommand(reviewDeps.commandTemplate, options?.commandTestTimeoutMs);
5701
5777
  if (result.ok) {
5702
5778
  log(`${icons.success} Command test ok (${(result.elapsedMs / 1e3).toFixed(1)}s)`);
5703
5779
  } else {
@@ -5959,7 +6035,7 @@ async function startBatchAgents(config, agents, pollIntervalMs, oauthToken, opti
5959
6035
  const { versionOverride, verbose, instancesOverride, agentOwner, userOrgs } = options;
5960
6036
  const client = new ApiClient(config.platformUrl, {
5961
6037
  authToken: oauthToken,
5962
- cliVersion: "0.23.7",
6038
+ cliVersion: "0.23.9",
5963
6039
  versionOverride,
5964
6040
  onTokenRefresh: () => getValidToken(config.platformUrl, { configPath: config.authFile })
5965
6041
  });
@@ -6005,7 +6081,8 @@ async function startBatchAgents(config, agents, pollIntervalMs, oauthToken, opti
6005
6081
  commandTemplate,
6006
6082
  maxDiffSizeKb: config.maxDiffSizeKb,
6007
6083
  maxRepoSizeMb: config.maxRepoSizeMb,
6008
- codebaseDir
6084
+ codebaseDir,
6085
+ livenessTimeoutMs: agentConfig.livenessTimeout != null ? agentConfig.livenessTimeout * 1e3 : void 0
6009
6086
  };
6010
6087
  const session = createSessionTracker();
6011
6088
  const usageTracker = new UsageTracker();
@@ -6054,7 +6131,10 @@ async function startBatchAgents(config, agents, pollIntervalMs, oauthToken, opti
6054
6131
  await Promise.all(
6055
6132
  agentStates.filter((state) => state.reviewDeps.commandTemplate && !state.routerRelay).map(async (state) => {
6056
6133
  state.logger.log("Testing command...");
6057
- const result = await testCommand(state.reviewDeps.commandTemplate);
6134
+ const result = await testCommand(
6135
+ state.reviewDeps.commandTemplate,
6136
+ config.commandTestTimeoutMs
6137
+ );
6058
6138
  if (result.ok) {
6059
6139
  state.logger.log(
6060
6140
  `${icons.success} Command test ok (${(result.elapsedMs / 1e3).toFixed(1)}s)`
@@ -6161,7 +6241,8 @@ async function startAgentRouter() {
6161
6241
  commandTemplate: commandTemplate ?? "",
6162
6242
  maxDiffSizeKb: config.maxDiffSizeKb,
6163
6243
  maxRepoSizeMb: config.maxRepoSizeMb,
6164
- codebaseDir
6244
+ codebaseDir,
6245
+ livenessTimeoutMs: agentConfig?.livenessTimeout != null ? agentConfig.livenessTimeout * 1e3 : void 0
6165
6246
  };
6166
6247
  const session = createSessionTracker();
6167
6248
  const usageTracker = new UsageTracker();
@@ -6197,7 +6278,8 @@ async function startAgentRouter() {
6197
6278
  userOrgs,
6198
6279
  usageLimits: config.usageLimits,
6199
6280
  versionOverride,
6200
- codebaseTtl: config.codebaseTtl
6281
+ codebaseTtl: config.codebaseTtl,
6282
+ commandTestTimeoutMs: config.commandTestTimeoutMs
6201
6283
  }
6202
6284
  );
6203
6285
  router.stop();
@@ -6228,7 +6310,8 @@ function startAgentByIndex(config, agentIndex, pollIntervalMs, oauthToken, versi
6228
6310
  commandTemplate,
6229
6311
  maxDiffSizeKb: config.maxDiffSizeKb,
6230
6312
  maxRepoSizeMb: config.maxRepoSizeMb,
6231
- codebaseDir
6313
+ codebaseDir,
6314
+ livenessTimeoutMs: agentConfig?.livenessTimeout != null ? agentConfig.livenessTimeout * 1e3 : void 0
6232
6315
  };
6233
6316
  const model = agentConfig?.model ?? "unknown";
6234
6317
  const tool = agentConfig?.tool ?? "unknown";
@@ -6274,7 +6357,8 @@ function startAgentByIndex(config, agentIndex, pollIntervalMs, oauthToken, versi
6274
6357
  codebaseTtl: config.codebaseTtl,
6275
6358
  verbose,
6276
6359
  agentOwner,
6277
- userOrgs
6360
+ userOrgs,
6361
+ commandTestTimeoutMs: config.commandTestTimeoutMs
6278
6362
  }
6279
6363
  ).finally(() => {
6280
6364
  routerRelay?.stop();
@@ -6302,7 +6386,7 @@ agentCommand.command("start").description("Start agents in polling mode").option
6302
6386
  }
6303
6387
  config = loadConfig();
6304
6388
  }
6305
- console.log(formatVersionBanner("0.23.7", "eb8ff83"));
6389
+ console.log(formatVersionBanner("0.23.9", "84eda5a"));
6306
6390
  if (config.agents && config.agents.length > 0) {
6307
6391
  const toolEntries = config.agents.map((a) => ({
6308
6392
  tool: a.tool,
@@ -7125,7 +7209,7 @@ var statusCommand = new Command4("status").description("Show agent config, conne
7125
7209
  });
7126
7210
 
7127
7211
  // src/index.ts
7128
- var program = new Command5().name("opencara").description("OpenCara \u2014 distributed AI code review agent").version(`${"0.23.7"} (${"eb8ff83"})`);
7212
+ var program = new Command5().name("opencara").description("OpenCara \u2014 distributed AI code review agent").version(`${"0.23.9"} (${"84eda5a"})`);
7129
7213
  program.addCommand(agentCommand);
7130
7214
  program.addCommand(authCommand());
7131
7215
  program.addCommand(dedupCommand());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencara",
3
- "version": "0.23.7",
3
+ "version": "0.23.9",
4
4
  "description": "Distributed AI code review agent — poll, review, and submit PR reviews using your own AI tools",
5
5
  "type": "module",
6
6
  "license": "MIT",