@patcherly/nodejs-connector 2.2.10 → 2.2.11

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/node_agent.js +57 -11
  2. package/package.json +1 -1
package/node_agent.js CHANGED
@@ -193,7 +193,7 @@ const { DEFAULT_API_URL, getConfiguredServerUrl, isExplicitApiBaseConfigured } =
193
193
  * update-release-latest.yml workflow so the value baked into every released tarball matches
194
194
  * the GitHub release tag. Reported to the API on every context upload.
195
195
  */
196
- const PATCHERLY_CONNECTOR_VERSION = '2.2.10';
196
+ const PATCHERLY_CONNECTOR_VERSION = '2.2.11';
197
197
  let CENTRAL_SERVER_URL = getConfiguredServerUrl();
198
198
  const IDS_PATH = process.env.PATCHERLY_IDS_PATH || path.join(__dirname, 'patcherly_ids.json');
199
199
  const QUEUE_PATH = process.env.PATCHERLY_QUEUE_PATH || path.join(__dirname, 'patcherly_queue.jsonl');
@@ -1250,6 +1250,47 @@ async function maybeRunPostApply(errorId, fixJson) {
1250
1250
  return telemetry;
1251
1251
  }
1252
1252
 
1253
+ /**
1254
+ * Start durable analysis (analyze-async) and wait for a terminal outcome via
1255
+ * analysis-wait long-polls. The central API owns retries; this helper sleeps
1256
+ * locally on retry_after_seconds between polls — no tight API spam.
1257
+ */
1258
+ async function analyzeAndWait(errorId) {
1259
+ const maxWallMs = 8 * 60 * 60 * 1000;
1260
+ const started = Date.now();
1261
+ const startPath = appPath('errors', String(errorId), 'analyze-async');
1262
+ const startHeaders = await signRequest('POST', startPath, '', { 'Content-Type': 'application/json' });
1263
+ const startEndpoint = buildApiEndpoint(startPath);
1264
+ const startResp = await fetch(startEndpoint, { method: 'POST', headers: startHeaders, body: '{}' });
1265
+ if (handleProtectionModeHttp(startResp.status, await startResp.clone().text())) {
1266
+ return { terminal: false, status: 'protection_mode', error_id: errorId };
1267
+ }
1268
+ if (!startResp.ok) {
1269
+ throw new Error(`analyze-async failed: ${startResp.status}`);
1270
+ }
1271
+
1272
+ const waitBasePath = appPath('errors', String(errorId), 'analysis-wait');
1273
+ const waitSignPath = `${waitBasePath}?timeout=120`;
1274
+ while (Date.now() - started < maxWallMs) {
1275
+ const waitEndpoint = `${buildApiEndpoint(waitBasePath)}?timeout=120`;
1276
+ const waitHeaders = await signRequest('GET', waitSignPath, '', {});
1277
+ const waitResp = await fetch(waitEndpoint, { method: 'GET', headers: waitHeaders });
1278
+ if (handleProtectionModeHttp(waitResp.status, await waitResp.clone().text())) {
1279
+ return { terminal: false, status: 'protection_mode', error_id: errorId };
1280
+ }
1281
+ if (!waitResp.ok) {
1282
+ throw new Error(`analysis-wait failed: ${waitResp.status}`);
1283
+ }
1284
+ const data = await waitResp.json();
1285
+ if (data.terminal) {
1286
+ return data;
1287
+ }
1288
+ const sleepSec = Math.max(5, Number(data.retry_after_seconds) || 30);
1289
+ await new Promise((resolve) => setTimeout(resolve, sleepSec * 1000));
1290
+ }
1291
+ throw new Error(`analysis-wait exceeded 8h wall clock for error ${errorId}`);
1292
+ }
1293
+
1253
1294
  async function processError(errorContext) {
1254
1295
  console.log('Processing error with context:', errorContext);
1255
1296
  try {
@@ -1265,9 +1306,12 @@ async function processError(errorContext) {
1265
1306
  // Update exclude_paths if cache is stale
1266
1307
  await updateExcludePaths();
1267
1308
 
1268
- // PRIMARY FILTERING: Check if error path is excluded BEFORE sending to server
1309
+ // PRIMARY FILTERING: require extractable source path; skip excluded paths
1269
1310
  const filePath = extractFilePath(errorContext);
1270
- if (filePath && isPathExcluded(filePath)) {
1311
+ if (!filePath) {
1312
+ return; // Not ingestable — no file to back up or patch
1313
+ }
1314
+ if (isPathExcluded(filePath)) {
1271
1315
  console.log(`Error from excluded path skipped: ${filePath}`);
1272
1316
  return; // Skip ingestion entirely - don't send to server
1273
1317
  }
@@ -1313,18 +1357,20 @@ async function processError(errorContext) {
1313
1357
  const autoApply = item.auto_apply === true;
1314
1358
  const ingestedStatus = item.status || 'pending';
1315
1359
 
1316
- if (!autoAnalyze || ['ignored', 'excluded', 'dismissed'].includes(ingestedStatus)) {
1360
+ if (!autoAnalyze || ['ignored', 'excluded', 'dismissed', 'analysis_failed'].includes(ingestedStatus)) {
1317
1361
  console.log(`Auto-analysis not enabled or error skipped (status=${ingestedStatus}); stopping after ingest.`);
1318
1362
  return;
1319
1363
  }
1320
1364
 
1321
- // analyze (always runs when autoAnalyze is true)
1322
- const path2 = appPath('errors', String(errorId), 'analyze');
1323
- const signedHeaders2 = await signRequest('POST', path2, '', { 'Content-Type': 'application/json' });
1324
- const endpoint2 = buildApiEndpoint(path2);
1325
- const r2 = await fetch(endpoint2, { method: 'POST', headers: signedHeaders2 });
1326
- if (handleProtectionModeHttp(r2.status, await r2.clone().text())) return;
1327
- if (!r2.ok) throw new Error(`analyze failed: ${r2.status}`);
1365
+ // analyze (always runs when autoAnalyze is true) — central retry via analyze-async + analysis-wait
1366
+ const analyzeOutcome = await analyzeAndWait(errorId);
1367
+ if (analyzeOutcome.status === 'analysis_failed') {
1368
+ console.warn('Analysis permanently failed after automatic retries; stopping auto-pipeline.');
1369
+ return;
1370
+ }
1371
+ if (analyzeOutcome.status === 'protection_mode') {
1372
+ return;
1373
+ }
1328
1374
 
1329
1375
  // v1.49: only chain into approve+apply when autoApply is also true. Otherwise the
1330
1376
  // human approves & applies the analyzed fix from the dashboard.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patcherly/nodejs-connector",
3
- "version": "2.2.10",
3
+ "version": "2.2.11",
4
4
  "description": "Patcherly detects bugs in real time in your app. It creates a customized AI patch, and once you approve it, backs up your code, fixes it & tests the patch for you. If anything is off, it rolls back the changes automatically, or you can always roll it back in a click. Website: https://patcherly.com · Docs: https://help.patcherly.com/connectors/nodejs/",
5
5
  "license": "MIT",
6
6
  "homepage": "https://patcherly.com",