antigravity-autopilot 1.4.3 → 1.4.4

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 (3) hide show
  1. package/extension.js +77 -0
  2. package/package.json +11 -1
  3. package/patcher.js +239 -26
package/extension.js CHANGED
@@ -343,6 +343,24 @@ class AntigravityPanelProvider {
343
343
  vscode.window.showInformationMessage(
344
344
  next ? '🛡️ Command Blocking enabled' : '⚠️ Command Blocking disabled',
345
345
  );
346
+ } else if (msg.command === 'toggleBrowserPermission') {
347
+ const cfg = vscode.workspace.getConfiguration('antigravityAutoAccept');
348
+ const current = cfg.get('autoAcceptBrowserPermission', true);
349
+ const next = !current;
350
+ await cfg.update('autoAcceptBrowserPermission', next, vscode.ConfigurationTarget.Global);
351
+ this.sendBrowserPermissionEnabled(next);
352
+ vscode.window.showInformationMessage(
353
+ next ? '🌐 Auto-accept browser permission enabled' : '🌐 Auto-accept browser permission disabled',
354
+ );
355
+ } else if (msg.command === 'toggleFilePermission') {
356
+ const cfg = vscode.workspace.getConfiguration('antigravityAutoAccept');
357
+ const current = cfg.get('autoAcceptFilePermission', true);
358
+ const next = !current;
359
+ await cfg.update('autoAcceptFilePermission', next, vscode.ConfigurationTarget.Global);
360
+ this.sendFilePermissionEnabled(next);
361
+ vscode.window.showInformationMessage(
362
+ next ? '📁 Auto-accept file permission enabled' : '📁 Auto-accept file permission disabled',
363
+ );
346
364
  } else if (msg.command === 'removePattern') {
347
365
  // Remove a built-in pattern by ID
348
366
  const removed = getRemovedPatternIds();
@@ -364,6 +382,12 @@ class AntigravityPanelProvider {
364
382
  this.sendBlockingEnabled(
365
383
  vscode.workspace.getConfiguration('antigravityAutoAccept').get('dangerousCommandBlocking.enabled', true),
366
384
  );
385
+ this.sendBrowserPermissionEnabled(
386
+ vscode.workspace.getConfiguration('antigravityAutoAccept').get('autoAcceptBrowserPermission', true),
387
+ );
388
+ this.sendFilePermissionEnabled(
389
+ vscode.workspace.getConfiguration('antigravityAutoAccept').get('autoAcceptFilePermission', true),
390
+ );
367
391
  this.sendPatterns();
368
392
  }
369
393
 
@@ -395,6 +419,18 @@ class AntigravityPanelProvider {
395
419
  this._view.webview.postMessage({ command: 'setBlockingEnabled', enabled });
396
420
  }
397
421
 
422
+ /** @param {boolean} enabled */
423
+ sendBrowserPermissionEnabled(enabled) {
424
+ if (!this._view) return;
425
+ this._view.webview.postMessage({ command: 'setBrowserPermissionEnabled', enabled });
426
+ }
427
+
428
+ /** @param {boolean} enabled */
429
+ sendFilePermissionEnabled(enabled) {
430
+ if (!this._view) return;
431
+ this._view.webview.postMessage({ command: 'setFilePermissionEnabled', enabled });
432
+ }
433
+
398
434
  /** Send current pattern list to the webview */
399
435
  sendPatterns() {
400
436
  if (!this._view) return;
@@ -633,6 +669,30 @@ class AntigravityPanelProvider {
633
669
  </label>
634
670
  </div>
635
671
 
672
+ <!-- Browser Permission toggle -->
673
+ <div class="toggle-row" id="browserPermToggleRow">
674
+ <div>
675
+ <div class="toggle-label">🌐 Browser Permission</div>
676
+ <div class="toggle-sub" id="browserPermToggleSub">Active — auto-accepting browser actions</div>
677
+ </div>
678
+ <label class="switch" title="Toggle auto-accept browser permission">
679
+ <input type="checkbox" id="browserPermToggleCheck" checked onchange="send('toggleBrowserPermission')">
680
+ <span class="slider"></span>
681
+ </label>
682
+ </div>
683
+
684
+ <!-- File Permission toggle -->
685
+ <div class="toggle-row" id="filePermToggleRow">
686
+ <div>
687
+ <div class="toggle-label">📁 File Permission</div>
688
+ <div class="toggle-sub" id="filePermToggleSub">Active — auto-allowing file access</div>
689
+ </div>
690
+ <label class="switch" title="Toggle auto-accept file permission">
691
+ <input type="checkbox" id="filePermToggleCheck" checked onchange="send('toggleFilePermission')">
692
+ <span class="slider"></span>
693
+ </label>
694
+ </div>
695
+
636
696
  <!-- Dangerous Command Blocking section -->
637
697
  <div class="section" id="blockSection">
638
698
  <div class="section-header" id="blockHeader">
@@ -660,6 +720,7 @@ class AntigravityPanelProvider {
660
720
 
661
721
  function send(cmd, extra) {
662
722
  if (cmd !== 'openSettings' && cmd !== 'toggleEnabled' && cmd !== 'toggleCommandBlocking'
723
+ && cmd !== 'toggleBrowserPermission' && cmd !== 'toggleFilePermission'
663
724
  && cmd !== 'refresh' && cmd !== 'removePattern' && cmd !== 'resetPatterns') {
664
725
  document.getElementById('btnApply').disabled = true;
665
726
  document.getElementById('btnRevert').disabled = true;
@@ -751,6 +812,22 @@ class AntigravityPanelProvider {
751
812
  if (section) section.style.opacity = data.enabled ? '1' : '0.4';
752
813
  }
753
814
 
815
+ if (data.command === 'setBrowserPermissionEnabled') {
816
+ const chk = document.getElementById('browserPermToggleCheck');
817
+ chk.checked = data.enabled;
818
+ document.getElementById('browserPermToggleSub').textContent = data.enabled
819
+ ? 'Active — auto-accepting browser actions'
820
+ : 'Disabled — browser actions require confirmation';
821
+ }
822
+
823
+ if (data.command === 'setFilePermissionEnabled') {
824
+ const chk = document.getElementById('filePermToggleCheck');
825
+ chk.checked = data.enabled;
826
+ document.getElementById('filePermToggleSub').textContent = data.enabled
827
+ ? 'Active — auto-allowing file access'
828
+ : 'Disabled — file access requires confirmation';
829
+ }
830
+
754
831
  if (data.command === 'patterns') {
755
832
  renderPatterns(data.patterns, data.totalBuiltin);
756
833
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "antigravity-autopilot",
3
3
  "displayName": "Antigravity AutoPilot",
4
4
  "description": "Enables autopilot mode for Antigravity: automatically executes all tool calls and terminal commands without manual confirmation. Patches the runtime JS bundle to inject auto-accept logic whenever the 'Always Proceed' policy is active — regex-based and version-agnostic.",
5
- "version": "1.4.3",
5
+ "version": "1.4.4",
6
6
  "license": "MIT",
7
7
  "publisher": "nguyen-hoang",
8
8
  "bin": {
@@ -109,6 +109,16 @@
109
109
  },
110
110
  "default": [],
111
111
  "description": "Additional regex patterns (JavaScript syntax) to treat as dangerous commands. Example: [\"^dd if=.*of=/dev/sd\", \"^shred\"]"
112
+ },
113
+ "antigravityAutoAccept.autoAcceptBrowserPermission": {
114
+ "type": "boolean",
115
+ "default": true,
116
+ "description": "Automatically accept browser action permissions (e.g. 'Agent needs permission to act on chromewebdata'). Requires patch to be applied."
117
+ },
118
+ "antigravityAutoAccept.autoAcceptFilePermission": {
119
+ "type": "boolean",
120
+ "default": true,
121
+ "description": "Automatically allow file access permissions with conversation scope. Requires patch to be applied."
112
122
  }
113
123
  }
114
124
  }
package/patcher.js CHANGED
@@ -189,13 +189,170 @@ function analyzeFile(content, label) {
189
189
  };
190
190
  }
191
191
 
192
+ // ─── Browser Action Permission (auto-confirm) ───────────────────────────────
193
+
194
+ /**
195
+ * Finds the JPc browser-action confirmation component and builds auto-confirm patch.
196
+ * Pattern: COMP=({sourceTrajectoryStepInfo:VAR,...,url:VAR})=>{...CONFIRM_FN=Mt(()=>{SEND(Ui(MSG,{...,interaction:{case:"browserAction",value:Ui(TYPE,{confirm:!0})}}))},...)...}
197
+ */
198
+ function analyzeBrowserAction(content, label) {
199
+ const log = (msg) => process.send({ type: 'log', msg: `[AutoAccept] [${label}] [browser] ${msg}` });
200
+
201
+ // 1. Find the browserAction confirm:!0 callback pattern
202
+ // VAR=Mt(()=>{SEND(Ui(MSG,{trajectoryId:VAR,stepIndex:VAR,interaction:{case:"browserAction",value:Ui(TYPE,{confirm:!0})}}))},DEPS)
203
+ const confirmRe = /(\w+)=Mt\(\(\)=>\{(\w+)\(Ui\((\w+),\{trajectoryId:(\w+),stepIndex:(\w+),interaction:\{case:"browserAction",value:Ui\((\w+),\{confirm:!0\}\)\}\}\)\)\},\[([\w,]*)\]\)/;
204
+ const confirmMatch = content.match(confirmRe);
205
+
206
+ if (!confirmMatch) {
207
+ log('❌ Could not find browserAction confirm pattern');
208
+ const idx = content.indexOf('browserAction');
209
+ if (idx >= 0) {
210
+ log(` Context: ...${content.slice(Math.max(0, idx - 80), idx + 120)}...`);
211
+ }
212
+ return null;
213
+ }
214
+
215
+ const [fullMatch, confirmVar] = confirmMatch;
216
+ const matchIndex = content.indexOf(fullMatch);
217
+ log(`✓ Found browserAction confirm at offset ${matchIndex}`);
218
+ log(` confirmVar=${confirmVar}`);
219
+
220
+ // 2. Find useEffect alias (reuse from nearby code)
221
+ const nearbyCode = content.substring(Math.max(0, matchIndex - 5000), matchIndex + 5000);
222
+ const effectCandidates = {};
223
+ const effectRe = /\b(\w{2,3})\(\(\)=>\{[^}]{3,80}\},\[/g;
224
+ let m;
225
+ while ((m = effectRe.exec(nearbyCode)) !== null) {
226
+ const alias = m[1];
227
+ if (alias !== 'Mt' && alias !== 'Vi' && alias !== 'var' && alias !== 'new') {
228
+ effectCandidates[alias] = (effectCandidates[alias] || 0) + 1;
229
+ }
230
+ }
231
+ const cleanupRe = /\b(\w{2,3})\(\(\)=>\{[^}]*return\s*\(\)=>/g;
232
+ while ((m = cleanupRe.exec(content)) !== null) {
233
+ const alias = m[1];
234
+ if (alias !== 'Mt' && alias !== 'Vi') {
235
+ effectCandidates[alias] = (effectCandidates[alias] || 0) + 5;
236
+ }
237
+ }
238
+ let useEffectAlias = null;
239
+ let maxCount = 0;
240
+ for (const [alias, count] of Object.entries(effectCandidates)) {
241
+ if (count > maxCount) {
242
+ maxCount = count;
243
+ useEffectAlias = alias;
244
+ }
245
+ }
246
+ if (!useEffectAlias) {
247
+ log('❌ Could not determine useEffect alias');
248
+ return null;
249
+ }
250
+ log(` useEffect=${useEffectAlias} (confidence: ${maxCount} hits)`);
251
+
252
+ // 3. Build patch — auto-call confirmVar() on mount
253
+ const patchCode = `_abp=${useEffectAlias}(()=>{${confirmVar}()},[${confirmVar}]),`;
254
+
255
+ return {
256
+ target: fullMatch,
257
+ replacement: patchCode + fullMatch,
258
+ patchMarker: `_abp=${useEffectAlias}(()=>{${confirmVar}()}`,
259
+ label
260
+ };
261
+ }
262
+
263
+ // ─── File Access Permission (auto-allow with conversation scope) ─────────────
264
+
265
+ /**
266
+ * Finds the rBe file-permission component and builds auto-allow patch.
267
+ * Pattern: COMP=({sourceTrajectoryStepInfo:VAR,req:VAR,status:VAR})=>{...SEND_FN...filePermission...scope...}
268
+ */
269
+ function analyzeFilePermission(content, label) {
270
+ const log = (msg) => process.send({ type: 'log', msg: `[AutoAccept] [${label}] [file] ${msg}` });
271
+
272
+ // 1. Find the filePermission sender pattern
273
+ // VAR=(ALLOW_VAR,SCOPE_VAR)=>{SEND(Ui(MSG,{trajectoryId:VAR,stepIndex:VAR,interaction:{case:"filePermission",value:Ui(TYPE,{allow:ALLOW_VAR,scope:SCOPE_VAR,absolutePathUri:REQ.absolutePathUri})}}))};
274
+ const senderRe = /(\w+)=\((\w+),(\w+)\)=>\{(\w+)\(Ui\((\w+),\{trajectoryId:(\w+),stepIndex:(\w+),interaction:\{case:"filePermission",value:Ui\((\w+),\{allow:\2,scope:\3,absolutePathUri:(\w+)\.absolutePathUri\}\)\}\}\)\)\}/;
275
+ const senderMatch = content.match(senderRe);
276
+
277
+ if (!senderMatch) {
278
+ log('❌ Could not find filePermission sender pattern');
279
+ const idx = content.indexOf('filePermission');
280
+ if (idx >= 0) {
281
+ log(` Context: ...${content.slice(Math.max(0, idx - 80), idx + 120)}...`);
282
+ }
283
+ return null;
284
+ }
285
+
286
+ const [fullMatch, senderVar, , , , , , , , reqVar] = senderMatch;
287
+ const matchIndex = content.indexOf(fullMatch);
288
+ log(`✓ Found filePermission sender at offset ${matchIndex}`);
289
+ log(` senderVar=${senderVar}, reqVar=${reqVar}`);
290
+
291
+ // 2. Find the scope enum (kot) — look for kot.CONVERSATION or similar near filePermission
292
+ // Pattern: o(!0,ENUM.CONVERSATION) in the Allow This Conversation button
293
+ const scopeRe = new RegExp(`${senderVar.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\(!0,(\w+)\.CONVERSATION\)`);
294
+ const scopeMatch = content.substring(matchIndex, matchIndex + 2000).match(scopeRe);
295
+
296
+ if (!scopeMatch) {
297
+ log('❌ Could not find scope enum (CONVERSATION)');
298
+ return null;
299
+ }
300
+ const scopeEnum = scopeMatch[1];
301
+ log(` scopeEnum=${scopeEnum}`);
302
+
303
+ // 3. Find useEffect alias
304
+ const nearbyCode = content.substring(Math.max(0, matchIndex - 5000), matchIndex + 5000);
305
+ const effectCandidates = {};
306
+ const effectRe = /\b(\w{2,3})\(\(\)=>\{[^}]{3,80}\},\[/g;
307
+ let m2;
308
+ while ((m2 = effectRe.exec(nearbyCode)) !== null) {
309
+ const alias = m2[1];
310
+ if (alias !== 'Mt' && alias !== 'Vi' && alias !== 'var' && alias !== 'new') {
311
+ effectCandidates[alias] = (effectCandidates[alias] || 0) + 1;
312
+ }
313
+ }
314
+ const cleanupRe = /\b(\w{2,3})\(\(\)=>\{[^}]*return\s*\(\)=>/g;
315
+ while ((m2 = cleanupRe.exec(content)) !== null) {
316
+ const alias = m2[1];
317
+ if (alias !== 'Mt' && alias !== 'Vi') {
318
+ effectCandidates[alias] = (effectCandidates[alias] || 0) + 5;
319
+ }
320
+ }
321
+ let useEffectAlias = null;
322
+ let maxCount = 0;
323
+ for (const [alias, count] of Object.entries(effectCandidates)) {
324
+ if (count > maxCount) {
325
+ maxCount = count;
326
+ useEffectAlias = alias;
327
+ }
328
+ }
329
+ if (!useEffectAlias) {
330
+ log('❌ Could not determine useEffect alias');
331
+ return null;
332
+ }
333
+ log(` useEffect=${useEffectAlias} (confidence: ${maxCount} hits)`);
334
+
335
+ // 4. Build patch — auto-call senderVar(!0, scopeEnum.CONVERSATION) on mount
336
+ const patchCode = `_afp=${useEffectAlias}(()=>{${senderVar}(!0,${scopeEnum}.CONVERSATION)},[${senderVar}]),`;
337
+
338
+ return {
339
+ target: fullMatch,
340
+ replacement: patchCode + fullMatch,
341
+ patchMarker: `_afp=${useEffectAlias}(()=>{${senderVar}(!0,${scopeEnum}.CONVERSATION)`,
342
+ label
343
+ };
344
+ }
345
+
192
346
  // ─── File Operations ─────────────────────────────────────────────────────────
193
347
 
194
348
  function isFilePatched(filePath) {
195
349
  if (!fs.existsSync(filePath)) return false;
196
350
  try {
197
351
  const content = fs.readFileSync(filePath, 'utf8');
198
- return content.includes('_aep=') && /_aep=\w+\(\(\)=>\{[^}]+EAGER/.test(content);
352
+ const hasTerminal = content.includes('_aep=') && /_aep=\w+\(\(\)=>\{[^}]+EAGER/.test(content);
353
+ const hasBrowser = content.includes('_abp=') && /_abp=\w+\(\(\)=>\{\w+\(\)\}/.test(content);
354
+ const hasFile = content.includes('_afp=') && /_afp=\w+\(\(\)=>\{\w+\(!0,/.test(content);
355
+ return hasTerminal || hasBrowser || hasFile;
199
356
  } catch {
200
357
  return false;
201
358
  }
@@ -215,33 +372,77 @@ function patchFile(filePath, label) {
215
372
  return false;
216
373
  }
217
374
 
218
- if (isFilePatched(filePath)) {
219
- process.send({ type: 'log', msg: `[AutoAccept] ⏭️ [${label}] Already patched` });
220
- return true;
221
- }
222
-
223
- const analysis = analyzeFile(content, label);
224
- if (!analysis) return false;
225
-
226
- // Verify target uniqueness
227
- const count = content.split(analysis.target).length - 1;
228
- if (count !== 1) {
229
- process.send({ type: 'log', msg: `[AutoAccept] ❌ [${label}] Target found ${count}x (expected 1)` });
230
- return false;
231
- }
232
-
233
- // Backup original
375
+ // Backup original (before any patching)
234
376
  const bakPath = filePath + '.bak';
235
377
  if (!fs.existsSync(bakPath)) {
236
378
  fs.copyFileSync(filePath, bakPath);
237
379
  process.send({ type: 'log', msg: `[AutoAccept] 📦 [${label}] Backup created` });
238
380
  }
239
381
 
240
- const patched = content.replace(analysis.target, analysis.replacement);
241
- fs.writeFileSync(filePath, patched, 'utf8');
382
+ let patched = content;
383
+ let anyPatched = false;
384
+
385
+ // ── Terminal auto-execute patch ──
386
+ if (!content.includes('_aep=')) {
387
+ const analysis = analyzeFile(content, label);
388
+ if (analysis) {
389
+ const count = patched.split(analysis.target).length - 1;
390
+ if (count === 1) {
391
+ patched = patched.replace(analysis.target, analysis.replacement);
392
+ anyPatched = true;
393
+ process.send({ type: 'log', msg: `[AutoAccept] ✅ [${label}] Terminal auto-execute patched` });
394
+ } else {
395
+ process.send({ type: 'log', msg: `[AutoAccept] ⚠️ [${label}] Terminal target found ${count}x (expected 1)` });
396
+ }
397
+ }
398
+ } else {
399
+ process.send({ type: 'log', msg: `[AutoAccept] ⏭️ [${label}] Terminal already patched` });
400
+ }
401
+
402
+ // ── Browser action auto-confirm patch ──
403
+ if (!patched.includes('_abp=')) {
404
+ const browserAnalysis = analyzeBrowserAction(patched, label);
405
+ if (browserAnalysis) {
406
+ const count = patched.split(browserAnalysis.target).length - 1;
407
+ if (count === 1) {
408
+ patched = patched.replace(browserAnalysis.target, browserAnalysis.replacement);
409
+ anyPatched = true;
410
+ process.send({ type: 'log', msg: `[AutoAccept] ✅ [${label}] Browser action auto-confirm patched` });
411
+ } else {
412
+ process.send({ type: 'log', msg: `[AutoAccept] ⚠️ [${label}] Browser target found ${count}x (expected 1)` });
413
+ }
414
+ }
415
+ } else {
416
+ process.send({ type: 'log', msg: `[AutoAccept] ⏭️ [${label}] Browser action already patched` });
417
+ }
242
418
 
243
- const sizeDiff = fs.statSync(filePath).size - fs.statSync(bakPath).size;
244
- process.send({ type: 'log', msg: `[AutoAccept] ✅ [${label}] Patched (+${sizeDiff} bytes)` });
419
+ // ── File permission auto-allow patch ──
420
+ if (!patched.includes('_afp=')) {
421
+ const fileAnalysis = analyzeFilePermission(patched, label);
422
+ if (fileAnalysis) {
423
+ const count = patched.split(fileAnalysis.target).length - 1;
424
+ if (count === 1) {
425
+ patched = patched.replace(fileAnalysis.target, fileAnalysis.replacement);
426
+ anyPatched = true;
427
+ process.send({ type: 'log', msg: `[AutoAccept] ✅ [${label}] File permission auto-allow patched` });
428
+ } else {
429
+ process.send({ type: 'log', msg: `[AutoAccept] ⚠️ [${label}] File target found ${count}x (expected 1)` });
430
+ }
431
+ }
432
+ } else {
433
+ process.send({ type: 'log', msg: `[AutoAccept] ⏭️ [${label}] File permission already patched` });
434
+ }
435
+
436
+ if (anyPatched) {
437
+ fs.writeFileSync(filePath, patched, 'utf8');
438
+ const sizeDiff = fs.statSync(filePath).size - fs.statSync(bakPath).size;
439
+ process.send({ type: 'log', msg: `[AutoAccept] ✅ [${label}] All patches applied (+${sizeDiff} bytes)` });
440
+ } else if (!content.includes('_aep=') && !content.includes('_abp=') && !content.includes('_afp=')) {
441
+ process.send({ type: 'log', msg: `[AutoAccept] ❌ [${label}] No patches could be applied` });
442
+ return false;
443
+ } else {
444
+ process.send({ type: 'log', msg: `[AutoAccept] ⏭️ [${label}] All patches already applied` });
445
+ }
245
446
  return true;
246
447
  }
247
448
 
@@ -266,11 +467,23 @@ process.on('message', (msg) => {
266
467
  process.exit(0);
267
468
  return;
268
469
  }
269
- const files = getTargetFiles(basePath).map(f => ({
270
- label: f.label,
271
- patched: isFilePatched(f.filePath),
272
- exists: fs.existsSync(f.filePath),
273
- }));
470
+ const files = getTargetFiles(basePath).map(f => {
471
+ let patchDetails = { terminal: false, browser: false, file: false };
472
+ if (fs.existsSync(f.filePath)) {
473
+ try {
474
+ const fc = fs.readFileSync(f.filePath, 'utf8');
475
+ patchDetails.terminal = fc.includes('_aep=') && /_aep=\w+\(\(\)=>\{[^}]+EAGER/.test(fc);
476
+ patchDetails.browser = fc.includes('_abp=') && /_abp=\w+\(\(\)=>\{\w+\(\)\}/.test(fc);
477
+ patchDetails.file = fc.includes('_afp=') && /_afp=\w+\(\(\)=>\{\w+\(!0,/.test(fc);
478
+ } catch { }
479
+ }
480
+ return {
481
+ label: f.label,
482
+ patched: patchDetails.terminal || patchDetails.browser || patchDetails.file,
483
+ patchDetails,
484
+ exists: fs.existsSync(f.filePath),
485
+ };
486
+ });
274
487
  process.send({ type: 'status', basePath, files });
275
488
  process.exit(0);
276
489