opencara 0.20.0 → 0.20.1

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 +102 -72
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4325,6 +4325,34 @@ var DEFAULT_RECHECK_INTERVAL = 50;
4325
4325
  var DEFAULT_POLL_INTERVAL_MS = 1e4;
4326
4326
  var MAX_CONSECUTIVE_AUTH_ERRORS = 3;
4327
4327
  var MAX_POLL_BACKOFF_MS = 3e5;
4328
+ var SHUTDOWN_GRACE_MS = 5e3;
4329
+ function registerShutdownHandlers(controller, log, graceMs = SHUTDOWN_GRACE_MS) {
4330
+ let shutdownInitiated = false;
4331
+ let forceTimer;
4332
+ const onSignal = (signal) => {
4333
+ if (shutdownInitiated) {
4334
+ log(`${icons.stop} Received ${signal} again \u2014 forcing exit`);
4335
+ process.exit(1);
4336
+ }
4337
+ shutdownInitiated = true;
4338
+ log(`${icons.stop} Received ${signal} \u2014 shutting down gracefully...`);
4339
+ controller.abort();
4340
+ forceTimer = setTimeout(() => {
4341
+ log(`${icons.stop} Shutdown timed out after ${graceMs / 1e3}s \u2014 forcing exit`);
4342
+ process.exit(1);
4343
+ }, graceMs);
4344
+ forceTimer.unref();
4345
+ };
4346
+ const onSigint = () => onSignal("SIGINT");
4347
+ const onSigterm = () => onSignal("SIGTERM");
4348
+ process.on("SIGINT", onSigint);
4349
+ process.on("SIGTERM", onSigterm);
4350
+ return () => {
4351
+ process.removeListener("SIGINT", onSigint);
4352
+ process.removeListener("SIGTERM", onSigterm);
4353
+ if (forceTimer) clearTimeout(forceTimer);
4354
+ };
4355
+ }
4328
4356
  var NON_RETRYABLE_STATUSES = /* @__PURE__ */ new Set([401, 403, 404]);
4329
4357
  function toApiDiffUrl(webUrl) {
4330
4358
  const match = webUrl.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)(?:\.diff)?$/);
@@ -5318,7 +5346,7 @@ function sleep2(ms, signal) {
5318
5346
  async function startAgent(agentId, platformUrl, agentInfo, reviewDeps, consumptionDeps, options) {
5319
5347
  const client = new ApiClient(platformUrl, {
5320
5348
  authToken: options?.authToken,
5321
- cliVersion: "0.20.0",
5349
+ cliVersion: "0.20.1",
5322
5350
  versionOverride: options?.versionOverride,
5323
5351
  onTokenRefresh: options?.onTokenRefresh
5324
5352
  });
@@ -5370,44 +5398,43 @@ async function startAgent(agentId, platformUrl, agentInfo, reviewDeps, consumpti
5370
5398
  }
5371
5399
  const cleanupTracker = ttlMs > 0 ? new CodebaseCleanupTracker(ttlMs) : void 0;
5372
5400
  const abortController = new AbortController();
5373
- process.on("SIGINT", () => {
5374
- abortController.abort();
5375
- });
5376
- process.on("SIGTERM", () => {
5377
- abortController.abort();
5378
- });
5379
- await pollLoop(client, agentId, reviewDeps, deps, agentInfo, logger, agentSession, {
5380
- pollIntervalMs: options?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
5381
- maxConsecutiveErrors: options?.maxConsecutiveErrors ?? DEFAULT_MAX_CONSECUTIVE_ERRORS,
5382
- routerRelay: options?.routerRelay,
5383
- reviewOnly: options?.reviewOnly,
5384
- repoConfig: options?.repoConfig,
5385
- roles: options?.roles,
5386
- synthesizeRepos: options?.synthesizeRepos,
5387
- signal: abortController.signal,
5388
- cleanupTracker,
5389
- verbose: options?.verbose,
5390
- agentOwner: options?.agentOwner,
5391
- userOrgs: options?.userOrgs
5392
- });
5393
- if (cleanupTracker && cleanupTracker.size > 0) {
5394
- const finalSwept = await cleanupTracker.sweep(cleanupWorktree);
5395
- if (finalSwept > 0) {
5401
+ const removeShutdownHandlers = registerShutdownHandlers(abortController, log);
5402
+ try {
5403
+ await pollLoop(client, agentId, reviewDeps, deps, agentInfo, logger, agentSession, {
5404
+ pollIntervalMs: options?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
5405
+ maxConsecutiveErrors: options?.maxConsecutiveErrors ?? DEFAULT_MAX_CONSECUTIVE_ERRORS,
5406
+ routerRelay: options?.routerRelay,
5407
+ reviewOnly: options?.reviewOnly,
5408
+ repoConfig: options?.repoConfig,
5409
+ roles: options?.roles,
5410
+ synthesizeRepos: options?.synthesizeRepos,
5411
+ signal: abortController.signal,
5412
+ cleanupTracker,
5413
+ verbose: options?.verbose,
5414
+ agentOwner: options?.agentOwner,
5415
+ userOrgs: options?.userOrgs
5416
+ });
5417
+ if (cleanupTracker && cleanupTracker.size > 0) {
5418
+ const finalSwept = await cleanupTracker.sweep(cleanupWorktree);
5419
+ if (finalSwept > 0) {
5420
+ log(
5421
+ `${icons.info} Cleaned up ${finalSwept} codebase director${finalSwept === 1 ? "y" : "ies"} on shutdown`
5422
+ );
5423
+ }
5424
+ }
5425
+ if (deps.usageTracker) {
5396
5426
  log(
5397
- `${icons.info} Cleaned up ${finalSwept} codebase director${finalSwept === 1 ? "y" : "ies"} on shutdown`
5427
+ deps.usageTracker.formatSummary(
5428
+ deps.usageLimits ?? usageLimits,
5429
+ deps.agentLimits,
5430
+ deps.agentId
5431
+ )
5398
5432
  );
5399
5433
  }
5434
+ log(formatExitSummary(agentSession));
5435
+ } finally {
5436
+ removeShutdownHandlers();
5400
5437
  }
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
5438
  }
5412
5439
  async function batchPollLoop(client, agentStates, options) {
5413
5440
  const {
@@ -5605,7 +5632,7 @@ async function startBatchAgents(config, agents, pollIntervalMs, oauthToken, opti
5605
5632
  const { versionOverride, verbose, instancesOverride, agentOwner, userOrgs } = options;
5606
5633
  const client = new ApiClient(config.platformUrl, {
5607
5634
  authToken: oauthToken,
5608
- cliVersion: "0.20.0",
5635
+ cliVersion: "0.20.1",
5609
5636
  versionOverride,
5610
5637
  onTokenRefresh: () => getValidToken(config.platformUrl, { configPath: config.authFile })
5611
5638
  });
@@ -5726,45 +5753,48 @@ async function startBatchAgents(config, agents, pollIntervalMs, oauthToken, opti
5726
5753
  }
5727
5754
  }
5728
5755
  const abortController = new AbortController();
5729
- process.on("SIGINT", () => abortController.abort());
5730
- process.on("SIGTERM", () => abortController.abort());
5756
+ const removeShutdownHandlers = registerShutdownHandlers(abortController, log);
5731
5757
  log(`${agentStates.length} agent instance(s) running in batch mode. Press Ctrl+C to stop.
5732
5758
  `);
5733
- await batchPollLoop(client, agentStates, {
5734
- pollIntervalMs,
5735
- maxConsecutiveErrors: config.maxConsecutiveErrors,
5736
- signal: abortController.signal,
5737
- accessibleRepos,
5738
- githubToken: oauthToken
5739
- });
5740
- await Promise.allSettled(
5741
- agentStates.map(async (state) => {
5742
- state.routerRelay?.stop();
5743
- if (state.cleanupTracker && state.cleanupTracker.size > 0) {
5744
- const swept = await state.cleanupTracker.sweep(cleanupWorktree);
5745
- if (swept > 0) {
5759
+ try {
5760
+ await batchPollLoop(client, agentStates, {
5761
+ pollIntervalMs,
5762
+ maxConsecutiveErrors: config.maxConsecutiveErrors,
5763
+ signal: abortController.signal,
5764
+ accessibleRepos,
5765
+ githubToken: oauthToken
5766
+ });
5767
+ await Promise.allSettled(
5768
+ agentStates.map(async (state) => {
5769
+ state.routerRelay?.stop();
5770
+ if (state.cleanupTracker && state.cleanupTracker.size > 0) {
5771
+ const swept = await state.cleanupTracker.sweep(cleanupWorktree);
5772
+ if (swept > 0) {
5773
+ state.logger.log(
5774
+ `${icons.info} Cleaned up ${swept} codebase director${swept === 1 ? "y" : "ies"} on shutdown`
5775
+ );
5776
+ }
5777
+ }
5778
+ if (state.consumptionDeps.usageTracker) {
5779
+ const limits = state.consumptionDeps.usageLimits ?? {
5780
+ maxTasksPerDay: null,
5781
+ maxTokensPerDay: null,
5782
+ maxTokensPerReview: null
5783
+ };
5746
5784
  state.logger.log(
5747
- `${icons.info} Cleaned up ${swept} codebase director${swept === 1 ? "y" : "ies"} on shutdown`
5785
+ state.consumptionDeps.usageTracker.formatSummary(
5786
+ limits,
5787
+ state.consumptionDeps.agentLimits,
5788
+ state.consumptionDeps.agentId
5789
+ )
5748
5790
  );
5749
5791
  }
5750
- }
5751
- if (state.consumptionDeps.usageTracker) {
5752
- const limits = state.consumptionDeps.usageLimits ?? {
5753
- maxTasksPerDay: null,
5754
- maxTokensPerDay: null,
5755
- maxTokensPerReview: null
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
- );
5792
+ state.logger.log(formatExitSummary(state.agentSession));
5793
+ })
5794
+ );
5795
+ } finally {
5796
+ removeShutdownHandlers();
5797
+ }
5768
5798
  }
5769
5799
  async function startAgentRouter() {
5770
5800
  const config = loadConfig();
@@ -5945,7 +5975,7 @@ agentCommand.command("start").description("Start agents in polling mode").option
5945
5975
  }
5946
5976
  config = loadConfig();
5947
5977
  }
5948
- console.log(formatVersionBanner("0.20.0", "787d0af"));
5978
+ console.log(formatVersionBanner("0.20.1", "c37d84d"));
5949
5979
  if (config.agents && config.agents.length > 0) {
5950
5980
  const toolEntries = config.agents.map((a) => ({
5951
5981
  tool: a.tool,
@@ -6768,7 +6798,7 @@ var statusCommand = new Command4("status").description("Show agent config, conne
6768
6798
  });
6769
6799
 
6770
6800
  // src/index.ts
6771
- var program = new Command5().name("opencara").description("OpenCara \u2014 distributed AI code review agent").version(`${"0.20.0"} (${"787d0af"})`);
6801
+ var program = new Command5().name("opencara").description("OpenCara \u2014 distributed AI code review agent").version(`${"0.20.1"} (${"c37d84d"})`);
6772
6802
  program.addCommand(agentCommand);
6773
6803
  program.addCommand(authCommand());
6774
6804
  program.addCommand(dedupCommand());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencara",
3
- "version": "0.20.0",
3
+ "version": "0.20.1",
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",