@wenathlan/devthink 2.0.12 → 2.0.14

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 (444) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/accounts.ts +2087 -0
  3. package/agent.ts +2331 -0
  4. package/antigravity.ts +2139 -0
  5. package/apifreeze.ts +4146 -0
  6. package/auth.ts +642 -0
  7. package/background.ts +16837 -0
  8. package/bridge.ts +2080 -0
  9. package/build.ts +54 -0
  10. package/bun.ts +26 -0
  11. package/capture.ts +1614 -0
  12. package/checksecrets.ts +72 -0
  13. package/cjs.ts +9 -0
  14. package/cli.ts +3500 -0
  15. package/commands.ts +1101 -0
  16. package/companion.ts +450 -0
  17. package/compatibility.ts +37 -0
  18. package/config.ts +3137 -0
  19. package/constants.ts +943 -0
  20. package/core.ts +1473 -0
  21. package/crossbrowser.ts +1359 -0
  22. package/dashdone.ts +605 -0
  23. package/data.ts +943 -0
  24. package/database.ts +133 -0
  25. package/debug.ts +850 -0
  26. package/deno.ts +27 -0
  27. package/devthink.ts +707 -0
  28. package/dist/accounts.d.ts +409 -0
  29. package/dist/agent.d.ts +837 -0
  30. package/dist/antigravity.d.ts +380 -0
  31. package/dist/apifreeze.d.ts +113 -0
  32. package/dist/auth.d.ts +224 -0
  33. package/{bridge.d.ts → dist/bridge.d.ts} +1 -1
  34. package/dist/capture.d.ts +595 -0
  35. package/dist/cjs.d.ts +6 -0
  36. package/{cli.js → dist/cli.js} +2 -2
  37. package/dist/commands.d.ts +310 -0
  38. package/dist/compatibility.d.ts +17 -0
  39. package/dist/config.d.ts +1853 -0
  40. package/dist/constants.d.ts +637 -0
  41. package/dist/core.d.ts +361 -0
  42. package/dist/data.d.ts +330 -0
  43. package/dist/database.d.ts +24 -0
  44. package/dist/debug.d.ts +270 -0
  45. package/dist/engine.d.ts +74 -0
  46. package/dist/environments.d.ts +244 -0
  47. package/dist/evidence.d.ts +277 -0
  48. package/dist/export.d.ts +311 -0
  49. package/dist/fingerprint.d.ts +594 -0
  50. package/dist/flow.d.ts +300 -0
  51. package/dist/gates.d.ts +103 -0
  52. package/dist/identity.d.ts +51 -0
  53. package/dist/ids.d.ts +7 -0
  54. package/dist/llm.d.ts +652 -0
  55. package/dist/models.d.ts +1807 -0
  56. package/dist/net.d.ts +325 -0
  57. package/dist/neutral.d.ts +41 -0
  58. package/dist/oauth.d.ts +703 -0
  59. package/dist/page.d.ts +1166 -0
  60. package/dist/perf.d.ts +481 -0
  61. package/dist/plan.d.ts +134 -0
  62. package/dist/project.d.ts +395 -0
  63. package/dist/providers.d.ts +37 -0
  64. package/dist/quota.d.ts +453 -0
  65. package/dist/recovery.d.ts +621 -0
  66. package/dist/request.d.ts +1492 -0
  67. package/dist/run.d.ts +779 -0
  68. package/dist/runtime.d.ts +212 -0
  69. package/dist/search.d.ts +375 -0
  70. package/dist/security.d.ts +534 -0
  71. package/dist/serve.d.ts +136 -0
  72. package/dist/session.d.ts +296 -0
  73. package/dist/storage.d.ts +53 -0
  74. package/dist/streaming.d.ts +1119 -0
  75. package/dist/swarm.d.ts +328 -0
  76. package/dist/tools.d.ts +170 -0
  77. package/dist/types.d.ts +8357 -0
  78. package/dist/utils.d.ts +82 -0
  79. package/dist/version.d.ts +7 -0
  80. package/dist/versionregistry.d.ts +406 -0
  81. package/dist/views.d.ts +579 -0
  82. package/dist/workbenchsession.d.ts +69 -0
  83. package/dist/workflow.d.ts +649 -0
  84. package/docs/readme.md +92 -0
  85. package/docs/schemas/antigravity.json +192 -0
  86. package/engine.ts +1524 -0
  87. package/environments.ts +1005 -0
  88. package/evidence.ts +643 -0
  89. package/export.ts +889 -0
  90. package/fingerprint.ts +1458 -0
  91. package/flow.ts +1190 -0
  92. package/gates.ts +307 -0
  93. package/gateway.ts +34 -0
  94. package/hardening.ts +191 -0
  95. package/headless.ts +369 -0
  96. package/http.ts +2110 -0
  97. package/identity.ts +246 -0
  98. package/ids.ts +26 -0
  99. package/index.ts +369 -0
  100. package/llm.ts +2682 -0
  101. package/mcp.ts +1835 -0
  102. package/{memory.js → memory.ts} +3834 -2310
  103. package/models.ts +3805 -0
  104. package/modes.ts +43 -0
  105. package/net.ts +1214 -0
  106. package/neutral.ts +362 -0
  107. package/node.ts +26 -0
  108. package/oauth.ts +2216 -0
  109. package/offscreen.ts +170 -0
  110. package/pack.ts +956 -0
  111. package/package.json +175 -82
  112. package/page.ts +7824 -0
  113. package/pagebridge.ts +1116 -0
  114. package/perf.ts +957 -0
  115. package/plan.ts +672 -0
  116. package/plugin.ts +76 -0
  117. package/{policy.js → policy.ts} +5073 -4679
  118. package/prisma.config.ts +27 -0
  119. package/progress.ts +1308 -0
  120. package/project.ts +1291 -0
  121. package/protocol.ts +4187 -0
  122. package/providers.ts +246 -0
  123. package/quota.ts +1887 -0
  124. package/recovery.ts +2433 -0
  125. package/request.ts +4482 -0
  126. package/run.ts +2135 -0
  127. package/runtime.ts +526 -0
  128. package/search.ts +1760 -0
  129. package/security.ts +1673 -0
  130. package/serve.ts +419 -0
  131. package/server.ts +5538 -0
  132. package/session.ts +1162 -0
  133. package/storage.ts +182 -0
  134. package/streaming.ts +4101 -0
  135. package/swarm.ts +884 -0
  136. package/sync.ts +56 -0
  137. package/tests/agentcert.test.ts +128 -0
  138. package/tests/agentfleet.test.ts +214 -0
  139. package/tests/agentreview.test.ts +241 -0
  140. package/tests/agentstream.test.ts +335 -0
  141. package/tests/agentwork.test.ts +356 -0
  142. package/tests/apifreeze.test.ts +290 -0
  143. package/tests/apimap.test.ts +112 -0
  144. package/tests/approvalgate.test.ts +175 -0
  145. package/tests/artifactmanifest.test.ts +204 -0
  146. package/tests/attentionfeed.test.ts +163 -0
  147. package/tests/backgroundruns.test.ts +100 -0
  148. package/tests/batchscheduling.test.ts +131 -0
  149. package/tests/blackboard.test.ts +238 -0
  150. package/tests/bridge.test.ts +1088 -0
  151. package/tests/browsercoverage.test.ts +134 -0
  152. package/tests/browserpolyfills.test.ts +124 -0
  153. package/tests/build.mjs +1260 -0
  154. package/tests/clientauth.test.ts +223 -0
  155. package/tests/clitools.test.ts +578 -0
  156. package/tests/confirmgates.test.ts +190 -0
  157. package/tests/containerpack.test.ts +143 -0
  158. package/tests/controlflow.test.ts +2120 -0
  159. package/tests/coordination.test.ts +345 -0
  160. package/tests/copyscreen.test.ts +139 -0
  161. package/tests/core.test.ts +417 -0
  162. package/tests/costcert.test.ts +95 -0
  163. package/tests/crossbrowser.test.ts +115 -0
  164. package/tests/cspaudit.test.ts +79 -0
  165. package/tests/dashdone.test.ts +615 -0
  166. package/tests/datagrid.test.ts +137 -0
  167. package/tests/doccheck.test.ts +107 -0
  168. package/tests/dombatch.test.ts +109 -0
  169. package/tests/emulation.test.ts +282 -0
  170. package/tests/environments.test.ts +259 -0
  171. package/tests/evidenceviews.test.ts +88 -0
  172. package/tests/exporttools.test.ts +185 -0
  173. package/tests/firefoxprep.test.ts +134 -0
  174. package/tests/flowlibrary.test.ts +242 -0
  175. package/tests/flowrun.test.ts +303 -0
  176. package/tests/forensics.test.ts +439 -0
  177. package/tests/gateway.test.ts +939 -0
  178. package/tests/headlesslib.test.ts +171 -0
  179. package/tests/httpstream.test.ts +277 -0
  180. package/tests/immutablelog.test.ts +157 -0
  181. package/tests/inboundguard.test.ts +177 -0
  182. package/tests/lazyload.test.ts +72 -0
  183. package/tests/librarymodes.test.ts +623 -0
  184. package/tests/llm.test.ts +696 -0
  185. package/tests/manifest.test.ts +226 -0
  186. package/tests/maskinputs.test.ts +150 -0
  187. package/tests/matrixverify.test.ts +111 -0
  188. package/tests/mavenpack.test.ts +75 -0
  189. package/tests/mcpserver.test.ts +704 -0
  190. package/tests/memory.test.ts +4155 -0
  191. package/tests/memorycare.test.ts +230 -0
  192. package/tests/migrateplan.test.ts +349 -0
  193. package/tests/minimization.test.ts +414 -0
  194. package/tests/modelroute.test.ts +112 -0
  195. package/tests/multiagent.test.ts +440 -0
  196. package/tests/nativebridge.test.ts +807 -0
  197. package/tests/navigation.test.ts +284 -0
  198. package/tests/nugetpack.test.ts +83 -0
  199. package/tests/orchestration.test.ts +449 -0
  200. package/tests/originpolicy.test.ts +405 -0
  201. package/tests/outputcompare.test.ts +97 -0
  202. package/tests/package.test.ts +91 -0
  203. package/tests/pagebridge.test.ts +5909 -0
  204. package/tests/pentest.test.ts +75 -0
  205. package/tests/perfrecords.test.ts +257 -0
  206. package/tests/permdiff.test.ts +85 -0
  207. package/tests/phishguard.test.ts +128 -0
  208. package/tests/pickerviews.test.ts +155 -0
  209. package/tests/pipelines.test.ts +381 -0
  210. package/tests/planlint.test.ts +240 -0
  211. package/tests/planreview.test.ts +383 -0
  212. package/tests/platformtargets.test.ts +56 -0
  213. package/tests/policy.test.ts +8284 -0
  214. package/tests/portability.test.ts +96 -0
  215. package/tests/progress.test.ts +1036 -0
  216. package/tests/promptlibrary.test.ts +116 -0
  217. package/tests/protocol.test.ts +4793 -0
  218. package/tests/publishingpipeline.test.ts +314 -0
  219. package/tests/quickactions.test.ts +164 -0
  220. package/tests/readiness.test.ts +78 -0
  221. package/tests/recipes.test.ts +163 -0
  222. package/tests/redactshots.test.ts +216 -0
  223. package/tests/registernvidiakeys.ts +51 -0
  224. package/tests/relayserve.test.ts +304 -0
  225. package/tests/releasenotes.test.ts +276 -0
  226. package/tests/resilience.test.ts +467 -0
  227. package/tests/resourceaware.test.ts +187 -0
  228. package/tests/runbudget.test.ts +125 -0
  229. package/tests/runhistory.test.ts +299 -0
  230. package/tests/runreplay.test.ts +113 -0
  231. package/tests/runresume.test.ts +184 -0
  232. package/tests/runstate.test.ts +288 -0
  233. package/tests/runtimeadapters.test.ts +251 -0
  234. package/tests/safariskeleton.test.ts +115 -0
  235. package/tests/sandboxframe.test.ts +196 -0
  236. package/tests/sbom.test.ts +105 -0
  237. package/tests/secretvault.test.ts +188 -0
  238. package/tests/selcache.test.ts +111 -0
  239. package/tests/sessioninterface.test.ts +791 -0
  240. package/tests/sessions.test.ts +591 -0
  241. package/tests/siteprefs.test.ts +228 -0
  242. package/tests/snapshotdelta.test.ts +165 -0
  243. package/tests/statusviews.test.ts +237 -0
  244. package/tests/stepmeter.test.ts +200 -0
  245. package/tests/streamparse.test.ts +163 -0
  246. package/tests/stylepruning.test.ts +90 -0
  247. package/tests/surfaces.test.ts +282 -0
  248. package/tests/sweep.test.ts +78 -0
  249. package/tests/syncbridge.test.ts +164 -0
  250. package/tests/taskqueue.test.ts +206 -0
  251. package/tests/toolcalls.test.ts +336 -0
  252. package/tests/toolcatalog.test.ts +201 -0
  253. package/tests/torture-agent.test.ts +706 -0
  254. package/tests/torture-auth.test.ts +461 -0
  255. package/tests/torture-commands.test.ts +895 -0
  256. package/tests/torture-data.test.ts +923 -0
  257. package/tests/torture-environments.test.ts +285 -0
  258. package/tests/torture-export.test.ts +263 -0
  259. package/tests/torture-http.test.ts +1379 -0
  260. package/tests/torture-llm.test.ts +808 -0
  261. package/tests/torture-memory.test.ts +1084 -0
  262. package/tests/torture-plan.test.ts +829 -0
  263. package/tests/torture-run.test.ts +626 -0
  264. package/tests/torture-session.test.ts +928 -0
  265. package/tests/torture-swarm.test.ts +1245 -0
  266. package/tests/torture-tools.test.ts +964 -0
  267. package/tests/torture-workflow.test.ts +2354 -0
  268. package/tests/tourviews.test.ts +52 -0
  269. package/tests/transparency.test.ts +147 -0
  270. package/tests/trigger.test.ts +941 -0
  271. package/tests/v1sunset.test.ts +428 -0
  272. package/tests/virtlist.test.ts +94 -0
  273. package/tests/vision.test.ts +540 -0
  274. package/tests/vsixpack.test.ts +145 -0
  275. package/tests/webapi.test.ts +523 -0
  276. package/tests/workflow.test.ts +1785 -0
  277. package/tests/workfloweditor.test.ts +759 -0
  278. package/tests/xpipack.test.ts +128 -0
  279. package/tools.ts +734 -0
  280. package/types.ts +9908 -0
  281. package/umd.ts +9 -0
  282. package/utils.ts +249 -0
  283. package/version.ts +8 -0
  284. package/versionregistry.ts +1310 -0
  285. package/views.ts +2153 -0
  286. package/vitest.config.ts +12 -0
  287. package/web/App.tsx +78 -0
  288. package/web/README.md +47 -0
  289. package/web/capacitor.config.ts +46 -0
  290. package/web/const.ts +19 -0
  291. package/web/control.shell.tsx +66 -0
  292. package/web/dashboardpage.ts +554 -0
  293. package/web/db.ts +493 -0
  294. package/web/design.html +541 -0
  295. package/web/gatewayview/Gateway.tsx +345 -0
  296. package/web/gatewayview/config.ts +1042 -0
  297. package/web/gatewayview/definition.ts +174 -0
  298. package/web/icons.ts +15 -0
  299. package/{manifest.json → web/manifest.json} +1 -1
  300. package/web/optionspage.ts +691 -0
  301. package/web/popup.ts +1651 -0
  302. package/web/schema.prisma +176 -0
  303. package/web/sidepanel.ts +7941 -0
  304. package/web/sitemanifest.json +13 -0
  305. package/web/terminal-ui.tsx +622 -0
  306. package/web/transparencypage.ts +541 -0
  307. package/web/vite.config.ts +39 -0
  308. package/web/workspace.ts +23 -0
  309. package/workbenchmemory.ts +104 -0
  310. package/workbenchsession.ts +304 -0
  311. package/workflow.ts +4495 -0
  312. package/bridge.d.ts.map +0 -1
  313. package/bridge.js +0 -1877
  314. package/bun.d.ts.map +0 -1
  315. package/bun.js +0 -52329
  316. package/caps/background.json +0 -712
  317. package/caps/cli.json +0 -360
  318. package/caps/library.json +0 -3611
  319. package/caps/mcp.json +0 -126
  320. package/caps/pagebridge.json +0 -377
  321. package/caps/popup.json +0 -51
  322. package/caps/sidepanel.json +0 -522
  323. package/checksums.txt +0 -142
  324. package/cli.d.ts.map +0 -1
  325. package/companion.d.ts.map +0 -1
  326. package/companion.js +0 -389
  327. package/crossbrowser.d.ts.map +0 -1
  328. package/crossbrowser.js +0 -1191
  329. package/dashdone.d.ts.map +0 -1
  330. package/dashdone.js +0 -368
  331. package/deno.d.ts.map +0 -1
  332. package/deno.js +0 -52325
  333. package/devthink.umd.js +0 -52346
  334. package/fixtures/bun.ts +0 -7
  335. package/fixtures/cjs.js +0 -6
  336. package/fixtures/deno.ts +0 -6
  337. package/fixtures/example-org-pagestate.json +0 -22
  338. package/fixtures/importers/automa-workflow.json +0 -18
  339. package/fixtures/importers/selenium-side.json +0 -20
  340. package/fixtures/importers/tabular-plan.csv +0 -7
  341. package/fixtures/importers/uivision-macro.json +0 -10
  342. package/fixtures/importers/v1-plan.json +0 -13
  343. package/fixtures/mcp-client.mjs +0 -14
  344. package/fixtures/pages/checkout.html +0 -192
  345. package/fixtures/pages/dashboard.html +0 -110
  346. package/fixtures/pages/feed.html +0 -113
  347. package/fixtures/pages/product-grid.html +0 -150
  348. package/fixtures/plans/form-inventory-plan.json +0 -11
  349. package/fixtures/plans/release-notes-plan.json +0 -11
  350. package/fixtures/recipes/agentscompete.json +0 -11
  351. package/fixtures/recipes/agentsforms.json +0 -11
  352. package/fixtures/recipes/agentsmonitor.json +0 -11
  353. package/fixtures/recipes/agentsreview.json +0 -29
  354. package/fixtures/recipes/agentswarm.json +0 -11
  355. package/fixtures/recipes/fillcalendar.json +0 -12
  356. package/fixtures/recipes/fillcheckout.json +0 -13
  357. package/fixtures/recipes/filldependentdropdowns.json +0 -12
  358. package/fixtures/recipes/fillfromcsv.json +0 -13
  359. package/fixtures/recipes/fillloginform.json +0 -12
  360. package/fixtures/recipes/fillmultistepwizard.json +0 -13
  361. package/fixtures/recipes/fillregistration.json +0 -12
  362. package/fixtures/recipes/fillsearchfilters.json +0 -12
  363. package/fixtures/recipes/filltypeahead.json +0 -11
  364. package/fixtures/recipes/monitoralerts.json +0 -12
  365. package/fixtures/recipes/monitoravailability.json +0 -11
  366. package/fixtures/recipes/monitornetworkquiet.json +0 -12
  367. package/fixtures/recipes/monitorpagechanges.json +0 -11
  368. package/fixtures/recipes/monitorpricechange.json +0 -11
  369. package/fixtures/recipes/monitorschedule.json +0 -12
  370. package/fixtures/recipes/scrapedynamicprices.json +0 -13
  371. package/fixtures/recipes/scrapeimagegallery.json +0 -12
  372. package/fixtures/recipes/scrapeinfinitefeed.json +0 -13
  373. package/fixtures/recipes/scrapenestedlists.json +0 -12
  374. package/fixtures/recipes/scrapeproductgrid.json +0 -12
  375. package/fixtures/recipes/scrapereviews.json +0 -12
  376. package/fixtures/recipes/scrapesearchresults.json +0 -12
  377. package/fixtures/recipes/scrapetablepagination.json +0 -12
  378. package/fixtures/recipes/testaccessibility.json +0 -34
  379. package/fixtures/recipes/testconsoleerrors.json +0 -12
  380. package/fixtures/recipes/testdeeplinks.json +0 -12
  381. package/fixtures/recipes/testformvalidation.json +0 -13
  382. package/fixtures/recipes/testlayoutshift.json +0 -12
  383. package/fixtures/recipes/testlinkcheck.json +0 -12
  384. package/fixtures/recipes/testloadtiming.json +0 -12
  385. package/fixtures/recipes/testvisualdiff.json +0 -12
  386. package/fixtures/workflows/changelog-digest-workflow.json +0 -13
  387. package/fixtures/workflows/form-survey-workflow.json +0 -13
  388. package/gallery.json +0 -706
  389. package/hardening.d.ts.map +0 -1
  390. package/hardening.js +0 -188
  391. package/headless.d.ts.map +0 -1
  392. package/headless.js +0 -11522
  393. package/http.d.ts.map +0 -1
  394. package/http.js +0 -2165
  395. package/index.cjs +0 -54682
  396. package/index.d.ts.map +0 -1
  397. package/index.js +0 -52310
  398. package/index.neutral.js +0 -46343
  399. package/mcp.d.ts.map +0 -1
  400. package/mcp.js +0 -20515
  401. package/memory.d.ts.map +0 -1
  402. package/nativehost.template.json +0 -10
  403. package/node.cjs +0 -54686
  404. package/node.d.ts.map +0 -1
  405. package/pack.d.ts.map +0 -1
  406. package/pack.js +0 -805
  407. package/policy.d.ts.map +0 -1
  408. package/progress.d.ts.map +0 -1
  409. package/progress.js +0 -645
  410. package/protocol.d.ts.map +0 -1
  411. package/protocol.js +0 -8334
  412. package/schemas/audit.schema.json +0 -226
  413. package/schemas/capability.schema.json +0 -113
  414. package/schemas/envelope.schema.json +0 -162
  415. package/schemas/memory.schema.json +0 -51
  416. package/schemas/observation.schema.json +0 -113
  417. package/schemas/plan.schema.json +0 -208
  418. package/schemas/progress.schema.json +0 -126
  419. package/schemas/proposal.schema.json +0 -68
  420. package/schemas/session.schema.json +0 -82
  421. package/schemas/tool.schema.json +0 -94
  422. package/server.d.ts.map +0 -1
  423. package/server.js +0 -28877
  424. package/umd-example.html +0 -18
  425. package/umd.d.ts.map +0 -1
  426. /package/{bun.d.ts → dist/bun.d.ts} +0 -0
  427. /package/{cli.d.ts → dist/cli.d.ts} +0 -0
  428. /package/{companion.d.ts → dist/companion.d.ts} +0 -0
  429. /package/{crossbrowser.d.ts → dist/crossbrowser.d.ts} +0 -0
  430. /package/{dashdone.d.ts → dist/dashdone.d.ts} +0 -0
  431. /package/{deno.d.ts → dist/deno.d.ts} +0 -0
  432. /package/{hardening.d.ts → dist/hardening.d.ts} +0 -0
  433. /package/{headless.d.ts → dist/headless.d.ts} +0 -0
  434. /package/{http.d.ts → dist/http.d.ts} +0 -0
  435. /package/{index.d.ts → dist/index.d.ts} +0 -0
  436. /package/{mcp.d.ts → dist/mcp.d.ts} +0 -0
  437. /package/{memory.d.ts → dist/memory.d.ts} +0 -0
  438. /package/{node.d.ts → dist/node.d.ts} +0 -0
  439. /package/{pack.d.ts → dist/pack.d.ts} +0 -0
  440. /package/{policy.d.ts → dist/policy.d.ts} +0 -0
  441. /package/{progress.d.ts → dist/progress.d.ts} +0 -0
  442. /package/{protocol.d.ts → dist/protocol.d.ts} +0 -0
  443. /package/{server.d.ts → dist/server.d.ts} +0 -0
  444. /package/{umd.d.ts → dist/umd.d.ts} +0 -0
@@ -0,0 +1,1310 @@
1
+ /* ── Merged: the grand merge section ── the correlated antigravity version resolution logics of the merged repository interned here, one surface without duplicate variations ── */
2
+ /**
3
+ * @module versions/registry
4
+ * @description
5
+ * Dynamic Antigravity version resolver of the merged repository
6
+ * (merged superset of the two prior variants).
7
+ *
8
+ * Two complementary resolution chains are unified here:
9
+ *
10
+ * Legacy chain (4 tiers):
11
+ * 1. Remote auto-updater manifests (linux_amd64.json / ide updater API)
12
+ * 2. Changelog scrape (antigravity.google.com, cloud.google.com, updater)
13
+ * 3. Stale disk cache (~/.config/opencode/antigravity-version.json)
14
+ * 4. Hardcoded fallback 1.15.8 / 2.0.11
15
+ *
16
+ * Observer chain (replicating Antigravity Manager startup):
17
+ * 1. Memory Map cache (6h TTL)
18
+ * 2. Remote registries: npm registry, GitHub Releases, storage.googleapis
19
+ * 3. Local cache file written atomically (tmp + rename, chmod 0600)
20
+ * 4. Governed fallback 1.19.2 (>= min supported, not blocked)
21
+ *
22
+ * Governance constants: ANTIGRAVITY_BLOCKED_VERSIONS hard-ban list,
23
+ * ANTIGRAVITY_VERSION_MIN_SUPPORTED ("1.15.8") and semver utilities
24
+ * (cleanSemver / compareSemver / shouldUpdate / isValidSemver /
25
+ * isBlockedVersion / isSupportedVersion).
26
+ *
27
+ * Reconciliation notes:
28
+ * - getVersion()/getVersionSync() accept BOTH call shapes (no args or options
29
+ * object) and consult both chains plus both cache stores. The observer
30
+ * chain is gated by min-supported/blocked checks; legacy chain results are
31
+ * honored unless explicitly blocked. When everything fails offline, the
32
+ * governed fallback 1.19.2 is returned by the merged getters, while the
33
+ * dedicated legacy getters (getVersionInfo*, fetchAntigravityVersion) keep
34
+ * their original 1.15.8 fallback untouched.
35
+ * - Cache persistence uses atomic tmp-file + rename with chmod 0600.
36
+ *
37
+ * Runtime: only node:* builtins + global fetch. Zero external deps. The
38
+ * registry is server and cli surface — the neutral core barrel never
39
+ * carries it, so the browser bundles stay free of node imports.
40
+ *
41
+ * @license MIT
42
+ */
43
+
44
+ import * as fs from "node:fs";
45
+ import * as fsp from "node:fs/promises";
46
+ import * as path from "node:path";
47
+ import * as os from "node:os";
48
+ import * as crypto from "node:crypto";
49
+
50
+ // Deduplicated against the frozen owner (constants.ts): symbols whose local
51
+ // definitions were value-identical are imported and re-exported below, so each
52
+ // has exactly one definition (library dedupe task 2-d1).
53
+ import {
54
+ ANTIGRAVITY_VERSION_FALLBACK,
55
+ ANTIGRAVITY_VERSION_MIN_SUPPORTED,
56
+ ANTIGRAVITY_BLOCKED_VERSIONS,
57
+ ANTIGRAVITY_VERSION_REMOTE_URLS,
58
+ VERSION_FETCH_TIMEOUT_MS,
59
+ FETCH_TIMEOUT_MS,
60
+ GEMINI_CLI_OAUTH_CLIENT_ID,
61
+ GEMINI_CLI_OAUTH_CLIENT_SECRET,
62
+ GEMINI_CLI_SCOPES,
63
+ GEMINI_CLI_USER_AGENT,
64
+ CLIENT_METADATA_STRING,
65
+ CLOUDCODE_BASE_URL,
66
+ CODE_ASSIST_PATH_MAP,
67
+ PROJECT_FALLBACK,
68
+ } from "./constants.js";
69
+ // v2.1.16 single-owner fix: the MODELS_2026 reference list comes from
70
+ // models.js (model identification owner — the 11-id legacy list).
71
+ import { MODELS_2026_LEGACY as MODELS_2026_REFERENCE } from "./models.js";
72
+ import { X_GOOG_API_CLIENT_GEMINI_CLI } from "./fingerprint.js";
73
+ import { fetchWithTimeout } from "./core.js";
74
+ import { isValidSemver } from "./config.js";
75
+ export { isValidSemver };
76
+
77
+ // Re-exports preserve this module's public surface after the dedupe.
78
+ export {
79
+ ANTIGRAVITY_VERSION_FALLBACK,
80
+ ANTIGRAVITY_VERSION_MIN_SUPPORTED,
81
+ ANTIGRAVITY_BLOCKED_VERSIONS,
82
+ ANTIGRAVITY_VERSION_REMOTE_URLS,
83
+ VERSION_FETCH_TIMEOUT_MS,
84
+ };
85
+
86
+ // ---------------------------------------------------------------------------
87
+ // Section: Constants / fallbacks (legacy chain)
88
+ // ---------------------------------------------------------------------------
89
+
90
+ /**
91
+ * Official Antigravity auto-updater endpoints (same infra as Gemini CLI /
92
+ * Antigravity IDE). Both return JSON with `version` shaped "1.15.8-57246...".
93
+ */
94
+ export const REMOTE_MANIFEST_URLS = [
95
+ "https://antigravity-auto-updater-974169037036.us-central1.run.app/manifests/linux_amd64.json",
96
+ "https://antigravity-ide-auto-updater-974169037036.us-central1.run.app/api/update/linux/x64/stable/0",
97
+ ] as const;
98
+
99
+ /** Changelog URLs for secondary scrape when manifests fail. */
100
+ export const CHANGELOG_URLS = [
101
+ "https://antigravity.google.com/docs/changelog",
102
+ "https://antigravity.google.com/changelog",
103
+ "https://cloud.google.com/antigravity/docs/release-notes",
104
+ "https://antigravity-auto-updater-974169037036.us-central1.run.app/changelog",
105
+ ] as const;
106
+
107
+ /** Hardened fallback - last known stable IDE version. */
108
+ export const FALLBACK_VERSION = "1.15.8" as const;
109
+ export const FALLBACK_FULL_VERSION = "1.15.8-5724687216017408" as const;
110
+
111
+ /** Alternative fallback - CLI/Antigravity v2 track compatibility. */
112
+ export const FALLBACK_ALTERNATIVE_VERSION = "2.0.11" as const;
113
+ export const FALLBACK_ALTERNATIVE_FULL = "2.0.11-0000000000000000" as const;
114
+
115
+ /** Legacy cache TTL: 6 hours in ms. */
116
+ export const VERSION_CACHE_TTL_MS = 6 * 60 * 60 * 1000;
117
+
118
+ /** Legacy cache file location - XDG style. */
119
+ export const VERSION_CACHE_FILE = path.join(os.homedir(), ".config", "opencode", "antigravity-version.json");
120
+
121
+ /** Default fetch timeout. */
122
+ // local variant: diverges from models (5s legacy-chain default vs models' 10s FETCH_TIMEOUT_MS)
123
+
124
+ // ---------------------------------------------------------------------------
125
+ // Section: Constants / governance (observer chain)
126
+ // ---------------------------------------------------------------------------
127
+
128
+ /**
129
+ * Governed fallback version, verified safe on 2026-08-25:
130
+ * versions below 1.15.8 are banned; 1.19.2 avoids the forced-update prompt.
131
+ */
132
+ // ANTIGRAVITY_VERSION_FALLBACK / ANTIGRAVITY_VERSION_MIN_SUPPORTED /
133
+ // ANTIGRAVITY_BLOCKED_VERSIONS / ANTIGRAVITY_VERSION_REMOTE_URLS /
134
+ // VERSION_FETCH_TIMEOUT_MS: identical values — imported and re-exported above.
135
+
136
+ /** Observer memory cache TTL - 6h. */
137
+ export const VERSION_MEMORY_TTL_MS = 6 * 60 * 60 * 1000;
138
+
139
+ /** Observer file cache TTL - 24h before considered stale (still usable). */
140
+ export const VERSION_FILE_TTL_MS = 24 * 60 * 60 * 1000;
141
+
142
+ const OBSERVER_CACHE_FILE_NAME = "antigravity-version.json";
143
+ const OBSERVER_FILE_MODE = 0o600;
144
+ const OBSERVER_DIR_MODE = 0o755;
145
+ const MEMORY_CACHE_KEY = "latest";
146
+
147
+ const SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)(?:-[\w.-]+)?(?:\+[\w.-]+)?$/;
148
+ const SEMVER_EXTRACT_RE = /(\d+\.\d+\.\d+(?:-[\w.-]+)?(?:\+[\w.-]+)?)/;
149
+
150
+ // ---------------------------------------------------------------------------
151
+ // Section: Auth constants (plugin integration reference, not used in fetch)
152
+ // v2.1.16 single-owner fix: every member is imported from its owner
153
+ // (constants.js raw values; models.js for the model-id list) — the former
154
+ // local literals were byte-identical duplicates.
155
+ // ---------------------------------------------------------------------------
156
+
157
+ export const ANTIGRAVITY_AUTH_CONSTANTS = {
158
+ CLIENT_ID: GEMINI_CLI_OAUTH_CLIENT_ID,
159
+ CLIENT_SECRET: GEMINI_CLI_OAUTH_CLIENT_SECRET,
160
+ SCOPES: GEMINI_CLI_SCOPES,
161
+ /** UA templates - production should vary platform dynamically.
162
+ * v2.1.16: derived from the canonical full UA by platform substitution
163
+ * (single owner: constants.GEMINI_CLI_USER_AGENT). */
164
+ USER_AGENT_TEMPLATES: {
165
+ darwin_arm64: GEMINI_CLI_USER_AGENT.replace("(linux; x64; GitHub)", "(darwin; arm64; GitHub)"),
166
+ darwin_x64: GEMINI_CLI_USER_AGENT.replace("(linux; x64; GitHub)", "(darwin; x64; GitHub)"),
167
+ linux_x64: GEMINI_CLI_USER_AGENT,
168
+ win32_x64: GEMINI_CLI_USER_AGENT.replace("(linux; x64; GitHub)", "(win32; x64; GitHub)"),
169
+ },
170
+ X_GOOG_API_CLIENT: X_GOOG_API_CLIENT_GEMINI_CLI,
171
+ CLIENT_METADATA: CLIENT_METADATA_STRING,
172
+ ENDPOINT_BASE: CLOUDCODE_BASE_URL,
173
+ ENDPOINTS: CODE_ASSIST_PATH_MAP,
174
+ PROJECT_FALLBACK: PROJECT_FALLBACK,
175
+ MODELS_2026: MODELS_2026_REFERENCE,
176
+ } as const;
177
+
178
+ // ---------------------------------------------------------------------------
179
+ // Section: Types
180
+ // ---------------------------------------------------------------------------
181
+
182
+ export type VersionSource =
183
+ | "remote-manifest"
184
+ | "ide-updater"
185
+ | "changelog"
186
+ | "cache-file"
187
+ | "memory-cache"
188
+ | "hardcoded-fallback";
189
+
190
+ export interface AntigravityVersionInfo {
191
+ /** Normalized version without build metadata, e.g. "1.15.8" */
192
+ version: string;
193
+ /** Full original version, e.g. "1.15.8-5724687216017408" */
194
+ fullVersion: string;
195
+ /** Where this version came from */
196
+ source: VersionSource;
197
+ /** Epoch ms */
198
+ fetchedAt: number;
199
+ /** Computed expiration */
200
+ expiresAt: number;
201
+ /** Optional URL that produced the version */
202
+ url?: string;
203
+ }
204
+
205
+ /** Observer memory-cache entry shape. */
206
+ export interface CacheEntry {
207
+ version: string;
208
+ fetchedAt: number; // epoch ms
209
+ source: string; // url or "file" or "memory"
210
+ }
211
+
212
+ /** Observer on-disk cache file shape. */
213
+ export interface VersionCacheFile {
214
+ version: string;
215
+ fetchedAt: number;
216
+ source: string;
217
+ expiresAt: number;
218
+ }
219
+
220
+ /** Observer fetch overrides. */
221
+ export interface FetchOptions {
222
+ /** Timeout per request ms - defaults to 5000 */
223
+ timeoutMs?: number;
224
+ /** Force remote fetch bypassing memory cache */
225
+ force?: boolean;
226
+ /** Custom remote URLs override (testing) */
227
+ remoteUrls?: readonly string[];
228
+ /** Custom cache path override */
229
+ cachePath?: string;
230
+ /** Extra User-Agent header value (GitHub rate-limit friendly) */
231
+ userAgent?: string;
232
+ }
233
+
234
+ export interface GetVersionOptions extends FetchOptions {
235
+ /** Allow returning stale file cache when remote fails - default true */
236
+ allowStale?: boolean;
237
+ }
238
+
239
+ /** Shape persisted on disk for the legacy chain. */
240
+ interface CachedFilePayload extends AntigravityVersionInfo {}
241
+
242
+ // ---------------------------------------------------------------------------
243
+ // Section: Internal state (legacy memory cache + dedup, observer Map)
244
+ // ---------------------------------------------------------------------------
245
+
246
+ let memoryCache: AntigravityVersionInfo | null = null;
247
+ let inflightPromise: Promise<AntigravityVersionInfo> | null = null;
248
+
249
+ /**
250
+ * Observer memory cache keyed by MEMORY_CACHE_KEY ("latest").
251
+ * Exported for introspection/testing; treat as read-only in production.
252
+ */
253
+ export const versionMemoryCache = new Map<string, CacheEntry>();
254
+
255
+ // ---------------------------------------------------------------------------
256
+ // Section: Semver utilities (observer chain, dependency-free)
257
+ // ---------------------------------------------------------------------------
258
+
259
+ /** Validates strict semver shape. */
260
+ // v2.1.15 Phase C: isValidSemver moved to config.js (validator owner) — re-exported below.
261
+
262
+ /**
263
+ * Extracts the first semver token from dirty input such as "v1.19.2" or
264
+ * "antigravity/1.19.2 darwin/arm64".
265
+ * @returns cleaned semver or null
266
+ */
267
+ export function cleanSemver(raw: unknown): string | null {
268
+ if (!raw || typeof raw !== "string") return null;
269
+ const trimmed = raw.trim();
270
+ if (SEMVER_RE.test(trimmed)) return trimmed;
271
+ const m = trimmed.match(SEMVER_EXTRACT_RE);
272
+ if (m && m[1] && SEMVER_RE.test(m[1])) return m[1];
273
+ return null;
274
+ }
275
+
276
+ /**
277
+ * Compares two semver strings.
278
+ * @returns -1 if a < b, 0 if equal, 1 if a > b (invalid sorts lowest)
279
+ */
280
+ export function compareSemver(a: string, b: string): -1 | 0 | 1 {
281
+ const ca = cleanSemver(a);
282
+ const cb = cleanSemver(b);
283
+ if (!ca && !cb) return 0;
284
+ if (!ca) return -1;
285
+ if (!cb) return 1;
286
+
287
+ const pa = ca.split(/[.-]/);
288
+ const pb = cb.split(/[.-]/);
289
+
290
+ for (let i = 0; i < 3; i++) {
291
+ const na = Number.parseInt(pa[i] ?? "0", 10);
292
+ const nb = Number.parseInt(pb[i] ?? "0", 10);
293
+ if (Number.isNaN(na) || Number.isNaN(nb)) continue;
294
+ if (na < nb) return -1;
295
+ if (na > nb) return 1;
296
+ }
297
+ // Prerelease suffix ordering: 1.19.2-preview < 1.19.2
298
+ const hasPreA = ca.includes("-");
299
+ const hasPreB = cb.includes("-");
300
+ if (hasPreA && !hasPreB) return -1;
301
+ if (!hasPreA && hasPreB) return 1;
302
+ return 0;
303
+ }
304
+
305
+ /** True when latest > current - triggers an update notification. */
306
+ export function shouldUpdate(current: string, latest: string): boolean {
307
+ const c = cleanSemver(current);
308
+ const l = cleanSemver(latest);
309
+ if (!c || !l) return false;
310
+ return compareSemver(c, l) === -1;
311
+ }
312
+
313
+ /** Checks whether a version is on the hard block list. */
314
+ export function isBlockedVersion(v: string): boolean {
315
+ const cleaned = cleanSemver(v);
316
+ if (!cleaned) return false;
317
+ return (ANTIGRAVITY_BLOCKED_VERSIONS as readonly string[]).includes(cleaned);
318
+ }
319
+
320
+ /** True when version >= min supported and not blocked. */
321
+ export function isSupportedVersion(v: string): boolean {
322
+ const cleaned = cleanSemver(v);
323
+ if (!cleaned) return false;
324
+ if (isBlockedVersion(cleaned)) return false;
325
+ return compareSemver(cleaned, ANTIGRAVITY_VERSION_MIN_SUPPORTED) >= 0;
326
+ }
327
+
328
+ // ---------------------------------------------------------------------------
329
+ // Section: Payload parsing - heterogeneous JSON shapes (npm/GitHub/GCS)
330
+ // ---------------------------------------------------------------------------
331
+
332
+ /**
333
+ * Extracts a version from unknown JSON payload shapes:
334
+ * npm `{version}`, GitHub `{tag_name}` / `{name}`, GCS `{version|latest|
335
+ * latestVersion}`, nested `{data:{version}}`; falls back to regex over the
336
+ * serialized payload.
337
+ */
338
+ export function parseVersionFromPayload(payload: unknown): string | null {
339
+ if (!payload || typeof payload !== "object") return null;
340
+ const obj = payload as Record<string, unknown>;
341
+
342
+ const candidates: unknown[] = [
343
+ obj["version"],
344
+ obj["latestVersion"],
345
+ obj["latest"],
346
+ obj["tag_name"],
347
+ obj["name"],
348
+ (obj["data"] as Record<string, unknown> | undefined)?.["version"],
349
+ (obj["data"] as Record<string, unknown> | undefined)?.["latestVersion"],
350
+ ];
351
+
352
+ for (const cand of candidates) {
353
+ if (typeof cand === "string") {
354
+ const cleaned = cleanSemver(cand);
355
+ if (cleaned) return cleaned;
356
+ }
357
+ }
358
+
359
+ try {
360
+ const asString = JSON.stringify(payload);
361
+ const m = asString.match(SEMVER_EXTRACT_RE);
362
+ if (m && m[1]) {
363
+ const cleaned = cleanSemver(m[1]);
364
+ if (cleaned) return cleaned;
365
+ }
366
+ } catch {
367
+ // ignore
368
+ }
369
+ return null;
370
+ }
371
+
372
+ // ---------------------------------------------------------------------------
373
+ // Section: Helpers (legacy chain)
374
+ // ---------------------------------------------------------------------------
375
+
376
+ /**
377
+ * Parses "1.15.8-5724687216017408" into normalized + full forms,
378
+ * keeping a safe fallback when the regex does not match.
379
+ */
380
+ export function parseAntigravityVersion(raw: string): {
381
+ normalized: string;
382
+ full: string;
383
+ } {
384
+ if (!raw || typeof raw !== "string") {
385
+ return { normalized: FALLBACK_VERSION, full: FALLBACK_FULL_VERSION };
386
+ }
387
+ const trimmed = raw.trim();
388
+ const m = trimmed.match(/(\d+\.\d+\.\d+(?:\.\d+)?)/);
389
+ if (!m) {
390
+ const cleaned = trimmed.replace(/[^0-9.\-]/g, "");
391
+ return {
392
+ normalized: cleaned.split("-")[0] || FALLBACK_VERSION,
393
+ full: trimmed,
394
+ };
395
+ }
396
+ return { normalized: m[1] || FALLBACK_VERSION, full: trimmed };
397
+ }
398
+
399
+ /**
400
+ * Fetch with timeout via AbortController (global fetch, Node 18+).
401
+ * Injects Gemini-CLI-like baseline headers; caller headers take precedence.
402
+ */
403
+ // local variant: diverges from models (injects Gemini-CLI baseline headers; no external-signal merge; 5s default)
404
+
405
+ /** Ensures the legacy cache directory exists. */
406
+ function ensureCacheDirSync(): void {
407
+ try {
408
+ const dir = path.dirname(VERSION_CACHE_FILE);
409
+ if (!fs.existsSync(dir)) {
410
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
411
+ }
412
+ } catch {
413
+ // silent - disk may be read-only, memory cache still works
414
+ }
415
+ }
416
+
417
+ /** Reads the legacy cache from disk synchronously when valid. */
418
+ export function readCacheFileSync(): AntigravityVersionInfo | null {
419
+ try {
420
+ if (!fs.existsSync(VERSION_CACHE_FILE)) return null;
421
+ const raw = fs.readFileSync(VERSION_CACHE_FILE, "utf8");
422
+ const parsed = JSON.parse(raw) as CachedFilePayload;
423
+ if (!parsed.version || !parsed.fetchedAt) return null;
424
+ if (Date.now() > parsed.expiresAt) return null;
425
+ return parsed;
426
+ } catch {
427
+ return null;
428
+ }
429
+ }
430
+
431
+ /** Reads the legacy cache from disk asynchronously. */
432
+ export async function readCacheFile(): Promise<AntigravityVersionInfo | null> {
433
+ try {
434
+ await fs.promises.access(VERSION_CACHE_FILE, fs.constants.R_OK);
435
+ const raw = await fs.promises.readFile(VERSION_CACHE_FILE, "utf8");
436
+ const parsed = JSON.parse(raw) as CachedFilePayload;
437
+ if (!parsed.version || !parsed.fetchedAt) return null;
438
+ if (Date.now() > parsed.expiresAt) return null;
439
+ return { ...parsed, source: "cache-file" };
440
+ } catch {
441
+ return null;
442
+ }
443
+ }
444
+
445
+ /**
446
+ * Writes the legacy cache file atomically: content goes to a unique temp
447
+ * file (chmod 0600), then rename over the destination. Best-effort, never
448
+ * throws into the caller.
449
+ */
450
+ export async function writeCacheFile(info: AntigravityVersionInfo): Promise<void> {
451
+ const dir = path.dirname(VERSION_CACHE_FILE);
452
+ try {
453
+ ensureCacheDirSync();
454
+ const payload: CachedFilePayload = { ...info };
455
+ const content = JSON.stringify(payload, null, 2);
456
+ const rand = crypto.randomBytes(6).toString("hex");
457
+ const tmp = path.join(dir, `.${path.basename(VERSION_CACHE_FILE)}.${process.pid}.${rand}.tmp`);
458
+ try {
459
+ await fsp.writeFile(tmp, content, { encoding: "utf8", mode: 0o600 });
460
+ try {
461
+ await fsp.chmod(tmp, 0o600);
462
+ } catch {
463
+ /* best effort */
464
+ }
465
+ await fsp.rename(tmp, VERSION_CACHE_FILE);
466
+ try {
467
+ await fsp.chmod(VERSION_CACHE_FILE, 0o600);
468
+ } catch {
469
+ /* best effort */
470
+ }
471
+ } finally {
472
+ try {
473
+ await fsp.unlink(tmp);
474
+ } catch {
475
+ /* temp may not exist anymore */
476
+ }
477
+ }
478
+ } catch {
479
+ // ignore - memory cache still works
480
+ }
481
+ }
482
+
483
+ // ---------------------------------------------------------------------------
484
+ // Section: Observer filesystem helpers
485
+ // ---------------------------------------------------------------------------
486
+
487
+ /**
488
+ * Resolves the observer cache dir, honoring OPENCODE_CONFIG_DIR and
489
+ * XDG_CONFIG_HOME (same contract as config.ts).
490
+ */
491
+ function resolveConfigDir(): string {
492
+ const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
493
+ if (envDir) return envDir;
494
+ const xdg = process.env.XDG_CONFIG_HOME?.trim();
495
+ if (xdg) return path.join(xdg, "opencode");
496
+ return path.join(os.homedir(), ".config", "opencode");
497
+ }
498
+
499
+ function resolveObserverCachePath(customPath?: string): string {
500
+ if (customPath?.trim()) return path.resolve(customPath.trim());
501
+ return path.join(resolveConfigDir(), OBSERVER_CACHE_FILE_NAME);
502
+ }
503
+
504
+ async function readObserverCacheFileAsync(cachePath: string): Promise<VersionCacheFile | null> {
505
+ try {
506
+ await fsp.access(cachePath, fs.constants.F_OK);
507
+ } catch {
508
+ return null;
509
+ }
510
+ try {
511
+ const text = await fsp.readFile(cachePath, "utf8");
512
+ if (!text.trim()) return null;
513
+ const parsed = JSON.parse(text) as VersionCacheFile;
514
+ const cleaned = cleanSemver(parsed?.version);
515
+ if (!cleaned) return null;
516
+ return {
517
+ version: cleaned,
518
+ fetchedAt: typeof parsed.fetchedAt === "number" ? parsed.fetchedAt : Date.now(),
519
+ source: typeof parsed.source === "string" ? parsed.source : "file",
520
+ expiresAt: typeof parsed.expiresAt === "number" ? parsed.expiresAt : Date.now() + VERSION_FILE_TTL_MS,
521
+ };
522
+ } catch {
523
+ return null;
524
+ }
525
+ }
526
+
527
+ function readObserverCacheFileSync(cachePath: string): VersionCacheFile | null {
528
+ try {
529
+ fs.accessSync(cachePath, fs.constants.F_OK);
530
+ } catch {
531
+ return null;
532
+ }
533
+ try {
534
+ const text = fs.readFileSync(cachePath, "utf8");
535
+ if (!text.trim()) return null;
536
+ const parsed = JSON.parse(text) as VersionCacheFile;
537
+ const cleaned = cleanSemver(parsed?.version);
538
+ if (!cleaned) return null;
539
+ return {
540
+ version: cleaned,
541
+ fetchedAt: typeof parsed.fetchedAt === "number" ? parsed.fetchedAt : Date.now(),
542
+ source: typeof parsed.source === "string" ? parsed.source : "file",
543
+ expiresAt: typeof parsed.expiresAt === "number" ? parsed.expiresAt : Date.now() + VERSION_FILE_TTL_MS,
544
+ };
545
+ } catch {
546
+ return null;
547
+ }
548
+ }
549
+
550
+ /**
551
+ * Atomic observer cache write: mkdir dir, unique tmp file with mode 0600,
552
+ * chmod hardening, rename over destination, tmp cleanup in finally.
553
+ */
554
+ async function writeObserverCacheFileAtomic(cachePath: string, entry: VersionCacheFile): Promise<void> {
555
+ const dir = path.dirname(cachePath);
556
+ try {
557
+ await fsp.mkdir(dir, { recursive: true, mode: OBSERVER_DIR_MODE });
558
+ } catch {
559
+ // best effort
560
+ }
561
+ try {
562
+ await fsp.chmod(dir, OBSERVER_DIR_MODE);
563
+ } catch {
564
+ // ignore chmod failures on some filesystems
565
+ }
566
+
567
+ const content = JSON.stringify(entry, null, 2) + "\n";
568
+ const rand = crypto.randomBytes(6).toString("hex");
569
+ const tmp = path.join(dir, `.${path.basename(cachePath)}.${process.pid}.${rand}.tmp`);
570
+
571
+ try {
572
+ await fsp.writeFile(tmp, content, { encoding: "utf8", mode: OBSERVER_FILE_MODE });
573
+ try {
574
+ await fsp.chmod(tmp, OBSERVER_FILE_MODE);
575
+ } catch {
576
+ /* best effort */
577
+ }
578
+ await fsp.rename(tmp, cachePath);
579
+ try {
580
+ await fsp.chmod(cachePath, OBSERVER_FILE_MODE);
581
+ } catch {
582
+ /* best effort */
583
+ }
584
+ } finally {
585
+ try {
586
+ await fsp.unlink(tmp);
587
+ } catch {
588
+ /* temp already renamed */
589
+ }
590
+ }
591
+ }
592
+
593
+ // ---------------------------------------------------------------------------
594
+ // Section: Remote fetchers - legacy chain (manifests + changelog)
595
+ // ---------------------------------------------------------------------------
596
+
597
+ /** Chain tier 1a: auto-updater manifests (heterogeneous JSON shapes). */
598
+ async function fetchFromManifest(url: string): Promise<AntigravityVersionInfo | null> {
599
+ try {
600
+ const res = await fetchWithTimeout(url, { method: "GET" });
601
+ if (!res.ok) return null;
602
+
603
+ const text = await res.text();
604
+ let json: any = null;
605
+ try {
606
+ json = JSON.parse(text);
607
+ } catch {
608
+ // Response may be plain text containing a version.
609
+ const parsed = parseAntigravityVersion(text);
610
+ if (parsed.normalized !== FALLBACK_VERSION || text.includes(".")) {
611
+ const now = Date.now();
612
+ return {
613
+ version: parsed.normalized,
614
+ fullVersion: parsed.full,
615
+ source: url.includes("ide-auto-updater") ? "ide-updater" : "remote-manifest",
616
+ fetchedAt: now,
617
+ expiresAt: now + VERSION_CACHE_TTL_MS,
618
+ url,
619
+ };
620
+ }
621
+ return null;
622
+ }
623
+
624
+ const candidates: (string | undefined)[] = [
625
+ json?.version,
626
+ json?.latestVersion,
627
+ json?.latest,
628
+ json?.stable?.version,
629
+ json?.manifest?.version,
630
+ json?.manifests?.linux_amd64?.version,
631
+ json?.manifests?.["linux/amd64"]?.version,
632
+ json?.data?.version,
633
+ json?.result?.version,
634
+ ];
635
+
636
+ if (Array.isArray(json?.manifests)) {
637
+ for (const m of json.manifests) {
638
+ if (m?.version) candidates.push(m.version);
639
+ }
640
+ }
641
+
642
+ // Bounded deep scan looking for any version-looking string.
643
+ if (candidates.every((c) => !c)) {
644
+ const stack = [json];
645
+ let depth = 0;
646
+ while (stack.length && depth < 20) {
647
+ const cur = stack.pop();
648
+ depth++;
649
+ if (cur && typeof cur === "object") {
650
+ for (const v of Object.values(cur)) {
651
+ if (typeof v === "string" && /^\d+\.\d+\.\d+-?\d*/.test(v)) {
652
+ candidates.push(v);
653
+ break;
654
+ }
655
+ if (v && typeof v === "object") stack.push(v);
656
+ }
657
+ }
658
+ if (candidates.filter(Boolean).length) break;
659
+ }
660
+ }
661
+
662
+ const rawVersion = candidates.find((c): c is string => !!c && typeof c === "string");
663
+ if (!rawVersion) return null;
664
+
665
+ const { normalized, full } = parseAntigravityVersion(rawVersion);
666
+ if (!normalized) return null;
667
+
668
+ const now = Date.now();
669
+ return {
670
+ version: normalized,
671
+ fullVersion: full,
672
+ source: url.includes("ide-auto-updater") ? "ide-updater" : "remote-manifest",
673
+ fetchedAt: now,
674
+ expiresAt: now + VERSION_CACHE_TTL_MS,
675
+ url,
676
+ };
677
+ } catch {
678
+ return null;
679
+ }
680
+ }
681
+
682
+ /** Chain tier 2: changelog scrape (best-effort regex extraction). */
683
+ async function fetchFromChangelog(): Promise<AntigravityVersionInfo | null> {
684
+ const versionRegex = /(?:antigravity[^0-9]{0,20}|version[^0-9]{0,10}|release[^0-9]{0,10})?(\d+\.\d+\.\d+(?:-\d+)?)/gi;
685
+ const semverOnly = /(\d+\.\d+\.\d+)/g;
686
+
687
+ for (const url of CHANGELOG_URLS) {
688
+ try {
689
+ const res = await fetchWithTimeout(url, { method: "GET" }, 5000);
690
+ if (!res.ok) continue;
691
+ const text = await res.text();
692
+ if (!text || text.length < 10) continue;
693
+
694
+ let match: RegExpExecArray | null;
695
+ const found: string[] = [];
696
+ while ((match = versionRegex.exec(text)) !== null) {
697
+ if (match[1]) found.push(match[1]);
698
+ if (found.length >= 10) break;
699
+ }
700
+
701
+ if (found.length === 0) {
702
+ let m2: RegExpExecArray | null;
703
+ while ((m2 = semverOnly.exec(text)) !== null) {
704
+ if (m2 && m2[1]) {
705
+ // Heuristic: skip tiny generic versions unless context matched.
706
+ const parts = m2[1].split(".").map(Number);
707
+ if ((parts[0] ?? 0) >= 1) found.push(m2[1]);
708
+ }
709
+ if (found.length >= 20) break;
710
+ }
711
+ }
712
+
713
+ if (found.length === 0) continue;
714
+
715
+ const raw = found[0] ?? "";
716
+ const { normalized, full } = parseAntigravityVersion(raw);
717
+ const now = Date.now();
718
+ return {
719
+ version: normalized,
720
+ fullVersion: full.includes("-") ? full : `${normalized}-0000000000000000`,
721
+ source: "changelog",
722
+ fetchedAt: now,
723
+ expiresAt: now + VERSION_CACHE_TTL_MS,
724
+ url,
725
+ };
726
+ } catch {
727
+ continue;
728
+ }
729
+ }
730
+ return null;
731
+ }
732
+
733
+ // ---------------------------------------------------------------------------
734
+ // Section: Remote fetch - observer chain (npm / GitHub / GCS)
735
+ // ---------------------------------------------------------------------------
736
+
737
+ /** Independent timeout fetch used by the observer chain. */
738
+ async function fetchLatestWithTimeout(url: string, timeoutMs: number, userAgent: string): Promise<Response> {
739
+ const controller = new AbortController();
740
+ const t = setTimeout(() => controller.abort(), timeoutMs);
741
+ try {
742
+ const res = await fetch(url, {
743
+ signal: controller.signal,
744
+ headers: {
745
+ Accept: "application/json, text/plain, */*",
746
+ "User-Agent": userAgent,
747
+ },
748
+ });
749
+ return res;
750
+ } finally {
751
+ clearTimeout(t);
752
+ }
753
+ }
754
+
755
+ /**
756
+ * Attempts to fetch the latest Antigravity version from the observer remote
757
+ * chain. Each URL gets an independent AbortController timeout. Valid semver
758
+ * results are stored in the memory Map and persisted atomically to disk.
759
+ *
760
+ * @param opts - optional overrides
761
+ * @returns latest version string or null when every remote fails
762
+ */
763
+ export async function fetchLatestVersion(opts: FetchOptions = {}): Promise<string | null> {
764
+ const timeoutMs = opts.timeoutMs ?? VERSION_FETCH_TIMEOUT_MS;
765
+ const remoteUrls = opts.remoteUrls ?? ANTIGRAVITY_VERSION_REMOTE_URLS;
766
+ const cachePath = resolveObserverCachePath(opts.cachePath);
767
+ const userAgent = opts.userAgent ?? `antigravity/${ANTIGRAVITY_VERSION_FALLBACK}`;
768
+
769
+ // Memory fast path unless forced.
770
+ if (!opts.force) {
771
+ const mem = versionMemoryCache.get(MEMORY_CACHE_KEY);
772
+ if (mem) {
773
+ const cleaned = cleanSemver(mem.version);
774
+ if (cleaned && Date.now() - mem.fetchedAt < VERSION_MEMORY_TTL_MS && isSupportedVersion(cleaned)) {
775
+ return cleaned;
776
+ }
777
+ }
778
+ }
779
+
780
+ for (const url of remoteUrls) {
781
+ try {
782
+ const res = await fetchLatestWithTimeout(url, timeoutMs, userAgent);
783
+ if (!res.ok) continue;
784
+
785
+ let payload: unknown;
786
+ const ct = res.headers.get("content-type") ?? "";
787
+ if (ct.includes("application/json") || ct.includes("text/json")) {
788
+ try {
789
+ payload = await res.json();
790
+ } catch {
791
+ const txt = await res.text();
792
+ try {
793
+ payload = JSON.parse(txt);
794
+ } catch {
795
+ payload = { version: txt.trim() };
796
+ }
797
+ }
798
+ } else {
799
+ // Some GCS buckets serve text/plain versions.
800
+ const txt = await res.text();
801
+ const trimmed = txt.trim();
802
+ try {
803
+ payload = JSON.parse(trimmed);
804
+ } catch {
805
+ payload = { version: trimmed };
806
+ }
807
+ }
808
+
809
+ const parsedVersion = parseVersionFromPayload(payload);
810
+ if (!parsedVersion) continue;
811
+ if (!isValidSemver(parsedVersion)) continue;
812
+ if (isBlockedVersion(parsedVersion)) continue;
813
+
814
+ // Success - populate both cache layers.
815
+ const entry: CacheEntry = {
816
+ version: parsedVersion,
817
+ fetchedAt: Date.now(),
818
+ source: url,
819
+ };
820
+ versionMemoryCache.set(MEMORY_CACHE_KEY, entry);
821
+
822
+ const fileEntry: VersionCacheFile = {
823
+ version: parsedVersion,
824
+ fetchedAt: entry.fetchedAt,
825
+ source: url,
826
+ expiresAt: Date.now() + VERSION_FILE_TTL_MS,
827
+ };
828
+ // Fire-and-forget persistence; observer never throws on cache writes.
829
+ try {
830
+ await writeObserverCacheFileAtomic(cachePath, fileEntry);
831
+ } catch {
832
+ // swallow - memory cache already saved
833
+ }
834
+
835
+ return parsedVersion;
836
+ } catch {
837
+ // Per-URL failure swallowed - continue down the chain.
838
+ continue;
839
+ }
840
+ }
841
+
842
+ return null;
843
+ }
844
+
845
+ // ---------------------------------------------------------------------------
846
+ // Section: Public API - legacy chain
847
+ // ---------------------------------------------------------------------------
848
+
849
+ /**
850
+ * Legacy main fetcher - resilient chain:
851
+ * 1. remote manifests (parallel, sequential consumption)
852
+ * 2. changelog scrape
853
+ * 3. expired disk cache as last resort before fallback
854
+ * 4. hardcoded fallback 1.15.8
855
+ *
856
+ * Always resolves with an AntigravityVersionInfo (fallback guaranteed).
857
+ */
858
+ export async function fetchAntigravityVersion(): Promise<AntigravityVersionInfo> {
859
+ const manifestPromises = REMOTE_MANIFEST_URLS.map((url) => fetchFromManifest(url));
860
+
861
+ for (const p of manifestPromises) {
862
+ try {
863
+ const result = await p;
864
+ if (result && result.version) {
865
+ memoryCache = result;
866
+ await writeCacheFile(result);
867
+ return result;
868
+ }
869
+ } catch {
870
+ // continue chain
871
+ }
872
+ }
873
+
874
+ try {
875
+ const changelogResult = await fetchFromChangelog();
876
+ if (changelogResult && changelogResult.version) {
877
+ memoryCache = changelogResult;
878
+ await writeCacheFile(changelogResult);
879
+ return changelogResult;
880
+ }
881
+ } catch {
882
+ // ignored
883
+ }
884
+
885
+ try {
886
+ if (fs.existsSync(VERSION_CACHE_FILE)) {
887
+ const raw = await fs.promises.readFile(VERSION_CACHE_FILE, "utf8");
888
+ const parsed = JSON.parse(raw) as CachedFilePayload;
889
+ if (parsed.version) {
890
+ const now = Date.now();
891
+ const reused: AntigravityVersionInfo = {
892
+ ...parsed,
893
+ fetchedAt: now,
894
+ expiresAt: now + VERSION_CACHE_TTL_MS,
895
+ source: "cache-file",
896
+ };
897
+ memoryCache = reused;
898
+ return reused;
899
+ }
900
+ }
901
+ } catch {
902
+ // ignored
903
+ }
904
+
905
+ const now = Date.now();
906
+ const fallback: AntigravityVersionInfo = {
907
+ version: FALLBACK_VERSION,
908
+ fullVersion: FALLBACK_FULL_VERSION,
909
+ source: "hardcoded-fallback",
910
+ fetchedAt: now,
911
+ expiresAt: now + VERSION_CACHE_TTL_MS,
912
+ };
913
+ memoryCache = fallback;
914
+ await writeCacheFile(fallback);
915
+ return fallback;
916
+ }
917
+
918
+ /**
919
+ * Legacy lazy getter - memory, then file, then fetch with promise dedup so
920
+ * concurrent calls share one inflight request.
921
+ */
922
+ export async function getVersionInfo(): Promise<AntigravityVersionInfo> {
923
+ if (memoryCache && Date.now() < memoryCache.expiresAt) {
924
+ return {
925
+ ...memoryCache,
926
+ source: memoryCache.source === "hardcoded-fallback" ? memoryCache.source : ("memory-cache" as VersionSource),
927
+ };
928
+ }
929
+
930
+ if (inflightPromise) {
931
+ return inflightPromise;
932
+ }
933
+
934
+ const fileCache = await readCacheFile();
935
+ if (fileCache) {
936
+ memoryCache = fileCache;
937
+ return fileCache;
938
+ }
939
+
940
+ inflightPromise = fetchAntigravityVersion().finally(() => {
941
+ inflightPromise = null;
942
+ });
943
+
944
+ return inflightPromise;
945
+ }
946
+
947
+ /**
948
+ * Synchronous complete-info getter - cache when present, otherwise the
949
+ * legacy fallback object (never touches the network).
950
+ */
951
+ export function getVersionInfoSync(): AntigravityVersionInfo {
952
+ if (memoryCache && Date.now() < memoryCache.expiresAt) {
953
+ return memoryCache;
954
+ }
955
+ const file = readCacheFileSync();
956
+ if (file) {
957
+ memoryCache = file;
958
+ return file;
959
+ }
960
+ const now = Date.now();
961
+ return {
962
+ version: FALLBACK_VERSION,
963
+ fullVersion: FALLBACK_FULL_VERSION,
964
+ source: "hardcoded-fallback",
965
+ fetchedAt: now,
966
+ expiresAt: now + VERSION_CACHE_TTL_MS,
967
+ };
968
+ }
969
+
970
+ /** Invalidates both legacy caches (memory + disk). */
971
+ export async function invalidateVersionCache(): Promise<void> {
972
+ memoryCache = null;
973
+ inflightPromise = null;
974
+ try {
975
+ await fs.promises.unlink(VERSION_CACHE_FILE);
976
+ } catch {
977
+ // missing or no permission - ignored
978
+ }
979
+ }
980
+
981
+ /** Invalidates only the legacy memory cache. */
982
+ export function invalidateMemoryCache(): void {
983
+ memoryCache = null;
984
+ inflightPromise = null;
985
+ }
986
+
987
+ // ---------------------------------------------------------------------------
988
+ // Section: Public API - merged getters (both behaviors preserved)
989
+ // ---------------------------------------------------------------------------
990
+
991
+ /**
992
+ * Merged lazy getter supporting BOTH historical call shapes:
993
+ *
994
+ * - `getVersion()` - legacy behavior: resolves through manifests/changelog,
995
+ * caches 6h in memory + disk, never rejects.
996
+ * - `getVersion({...})` - observer behavior: honors force/timeoutMs/
997
+ * remoteUrls/cachePath/userAgent/allowStale, gates results through
998
+ * isSupportedVersion/isBlockedVersion, persists atomically.
999
+ *
1000
+ * Resolution order: observer memory Map -> legacy memory info ->
1001
+ * observer remote chain (npm/GitHub/GCS) -> legacy remote chain
1002
+ * (manifests/changelog, results rejected only when explicitly blocked or
1003
+ * merely the legacy hardcoded fallback) -> both file caches ->
1004
+ * governed fallback ANTIGRAVITY_VERSION_FALLBACK ("1.19.2").
1005
+ */
1006
+ export async function getVersion(opts: GetVersionOptions = {}): Promise<string> {
1007
+ const force = opts.force === true;
1008
+
1009
+ // 0a) observer memory cache
1010
+ if (!force) {
1011
+ const mem = versionMemoryCache.get(MEMORY_CACHE_KEY);
1012
+ if (mem) {
1013
+ const cleaned = cleanSemver(mem.version);
1014
+ if (cleaned && Date.now() - mem.fetchedAt < VERSION_MEMORY_TTL_MS && isSupportedVersion(cleaned)) {
1015
+ return cleaned;
1016
+ }
1017
+ }
1018
+ // 0b) legacy memory info cache
1019
+ if (memoryCache && Date.now() < memoryCache.expiresAt && !isBlockedVersion(memoryCache.version)) {
1020
+ return memoryCache.version;
1021
+ }
1022
+ }
1023
+
1024
+ // 1) remote chains: observer first (gated), then legacy manifests/changelog.
1025
+ let resolved: string | null = null;
1026
+ try {
1027
+ const remote = await fetchLatestVersion(opts);
1028
+ if (remote && isSupportedVersion(remote)) {
1029
+ resolved = remote;
1030
+ }
1031
+ } catch {
1032
+ // swallowed - fall through
1033
+ }
1034
+
1035
+ if (!resolved) {
1036
+ try {
1037
+ const legacy = await fetchAntigravityVersion();
1038
+ if (legacy && legacy.version && legacy.source !== "hardcoded-fallback" && !isBlockedVersion(legacy.version)) {
1039
+ resolved = legacy.version;
1040
+ versionMemoryCache.set(MEMORY_CACHE_KEY, {
1041
+ version: legacy.version,
1042
+ fetchedAt: legacy.fetchedAt,
1043
+ source: legacy.url ?? legacy.source,
1044
+ });
1045
+ }
1046
+ } catch {
1047
+ // swallowed - fall through
1048
+ }
1049
+ }
1050
+
1051
+ if (resolved) return resolved;
1052
+
1053
+ // 2) file caches: observer path (stale allowed by default) + legacy path.
1054
+ const allowStale = opts.allowStale ?? true;
1055
+ const observerPath = resolveObserverCachePath(opts.cachePath);
1056
+
1057
+ try {
1058
+ const fileCache = await readObserverCacheFileAsync(observerPath);
1059
+ if (fileCache) {
1060
+ const fresh = Date.now() < fileCache.expiresAt;
1061
+ if (fresh || allowStale) {
1062
+ const cleaned = cleanSemver(fileCache.version);
1063
+ if (cleaned && isSupportedVersion(cleaned)) {
1064
+ versionMemoryCache.set(MEMORY_CACHE_KEY, {
1065
+ version: cleaned,
1066
+ fetchedAt: fileCache.fetchedAt,
1067
+ source: fileCache.source,
1068
+ });
1069
+ return cleaned;
1070
+ }
1071
+ }
1072
+ }
1073
+ } catch {
1074
+ // swallowed
1075
+ }
1076
+
1077
+ try {
1078
+ const legacyFile = await readCacheFile();
1079
+ if (legacyFile && !isBlockedVersion(legacyFile.version)) {
1080
+ return legacyFile.version;
1081
+ }
1082
+ } catch {
1083
+ // swallowed
1084
+ }
1085
+
1086
+ // 3) governed fallback - guaranteed valid semver.
1087
+ const fallback = ANTIGRAVITY_VERSION_FALLBACK;
1088
+ versionMemoryCache.set(MEMORY_CACHE_KEY, {
1089
+ version: fallback,
1090
+ fetchedAt: Date.now(),
1091
+ source: "fallback",
1092
+ });
1093
+ return fallback;
1094
+ }
1095
+
1096
+ /**
1097
+ * Merged synchronous getter (no network). Supports both call shapes:
1098
+ * `getVersionSync()` (legacy) and `getVersionSync({cachePath,allowStale})`
1099
+ * (observer). Reads observer Map -> legacy memory -> observer file ->
1100
+ * legacy file -> governed fallback 1.19.2.
1101
+ */
1102
+ export function getVersionSync(opts: { cachePath?: string; allowStale?: boolean } = {}): string {
1103
+ const allowStale = opts.allowStale ?? true;
1104
+ const observerPath = resolveObserverCachePath(opts.cachePath);
1105
+
1106
+ const mem = versionMemoryCache.get(MEMORY_CACHE_KEY);
1107
+ if (mem) {
1108
+ const cleaned = cleanSemver(mem.version);
1109
+ if (cleaned && Date.now() - mem.fetchedAt < VERSION_MEMORY_TTL_MS && isSupportedVersion(cleaned)) {
1110
+ return cleaned;
1111
+ }
1112
+ }
1113
+
1114
+ if (memoryCache && Date.now() < memoryCache.expiresAt && !isBlockedVersion(memoryCache.version)) {
1115
+ return memoryCache.version;
1116
+ }
1117
+
1118
+ const fileCache = readObserverCacheFileSync(observerPath);
1119
+ if (fileCache) {
1120
+ const fresh = Date.now() < fileCache.expiresAt;
1121
+ if (fresh || allowStale) {
1122
+ const cleaned = cleanSemver(fileCache.version);
1123
+ if (cleaned && isSupportedVersion(cleaned)) {
1124
+ versionMemoryCache.set(MEMORY_CACHE_KEY, {
1125
+ version: cleaned,
1126
+ fetchedAt: fileCache.fetchedAt,
1127
+ source: fileCache.source,
1128
+ });
1129
+ return cleaned;
1130
+ }
1131
+ }
1132
+ }
1133
+
1134
+ const legacyFile = readCacheFileSync();
1135
+ if (legacyFile && !isBlockedVersion(legacyFile.version)) {
1136
+ return legacyFile.version;
1137
+ }
1138
+
1139
+ return ANTIGRAVITY_VERSION_FALLBACK;
1140
+ }
1141
+
1142
+ // ---------------------------------------------------------------------------
1143
+ // Section: Cache invalidation - observer chain
1144
+ // ---------------------------------------------------------------------------
1145
+
1146
+ /**
1147
+ * Clears the observer memory entry and removes its cache file.
1148
+ * Use {@link invalidateVersionCache} for the legacy stores.
1149
+ */
1150
+ export async function clearVersionCache(opts: { cachePath?: string } = {}): Promise<void> {
1151
+ versionMemoryCache.delete(MEMORY_CACHE_KEY);
1152
+ const cachePath = resolveObserverCachePath(opts.cachePath);
1153
+ try {
1154
+ await fsp.unlink(cachePath);
1155
+ } catch {
1156
+ // ignore missing
1157
+ }
1158
+ }
1159
+
1160
+ /** Synchronous variant of {@link clearVersionCache}. */
1161
+ export function clearVersionCacheSync(opts: { cachePath?: string } = {}): void {
1162
+ versionMemoryCache.delete(MEMORY_CACHE_KEY);
1163
+ const cachePath = resolveObserverCachePath(opts.cachePath);
1164
+ try {
1165
+ fs.unlinkSync(cachePath);
1166
+ } catch {
1167
+ // ignore missing
1168
+ }
1169
+ }
1170
+
1171
+ // ---------------------------------------------------------------------------
1172
+ // Section: User-Agent helpers
1173
+ // ---------------------------------------------------------------------------
1174
+
1175
+ /**
1176
+ * Dynamic legacy UA varying platform (darwin/linux/win32) for coherent
1177
+ * bypass; embeds the fetched Antigravity version as a safe comment segment.
1178
+ */
1179
+ // local variant: diverges from constants (async template-based GeminiCLI UA vs constants' sync antigravity/{ver} {os}/{arch} UA)
1180
+ export async function getVersionedUserAgent(): Promise<string> {
1181
+ const platform = os.platform();
1182
+ const arch = os.arch();
1183
+ const key = `${platform}_${arch}` as keyof typeof ANTIGRAVITY_AUTH_CONSTANTS.USER_AGENT_TEMPLATES;
1184
+ const template =
1185
+ ANTIGRAVITY_AUTH_CONSTANTS.USER_AGENT_TEMPLATES[key] ||
1186
+ (platform === "darwin"
1187
+ ? ANTIGRAVITY_AUTH_CONSTANTS.USER_AGENT_TEMPLATES.darwin_arm64
1188
+ : ANTIGRAVITY_AUTH_CONSTANTS.USER_AGENT_TEMPLATES.linux_x64);
1189
+
1190
+ const ver = await getVersion().catch(() => FALLBACK_VERSION);
1191
+ return template.replace("GitHub)", `GitHub; Antigravity/${ver})`);
1192
+ }
1193
+
1194
+ /** Simple semver gt comparison retained from the legacy API. */
1195
+ export function isNewerVersion(a: string, b: string): boolean {
1196
+ const toNums = (s: string) => (s.split("-")[0] ?? "").split(".").map((n) => parseInt(n, 10) || 0);
1197
+ const A = toNums(a);
1198
+ const B = toNums(b);
1199
+ for (let i = 0; i < Math.max(A.length, B.length); i++) {
1200
+ const av = A[i] || 0;
1201
+ const bv = B[i] || 0;
1202
+ if (av > bv) return true;
1203
+ if (av < bv) return false;
1204
+ }
1205
+ return false;
1206
+ }
1207
+
1208
+ /**
1209
+ * Observer-style User-Agent for a given version:
1210
+ * `antigravity/{version} {platform}/{arch}` (coherent per host).
1211
+ * @param version - semver; falls back to ANTIGRAVITY_VERSION_FALLBACK
1212
+ */
1213
+ export function getAntigravityUserAgent(version?: string): string {
1214
+ const ver = cleanSemver(version ?? "") ?? ANTIGRAVITY_VERSION_FALLBACK;
1215
+ const plat = os.platform();
1216
+ const arch = os.arch();
1217
+ return `antigravity/${ver} ${plat}/${arch}`;
1218
+ }
1219
+
1220
+ /**
1221
+ * Async observer UA ensuring the latest version first. Pass cachedVersion to
1222
+ * avoid network round-trips.
1223
+ */
1224
+ export async function getDynamicUserAgentVersioned(cachedVersion?: string, opts: FetchOptions = {}): Promise<string> {
1225
+ const ver = cachedVersion && isValidSemver(cachedVersion) ? cachedVersion : await getVersion(opts);
1226
+ return getAntigravityUserAgent(ver);
1227
+ }
1228
+
1229
+ // ---------------------------------------------------------------------------
1230
+ // Section: Barrels
1231
+ // ---------------------------------------------------------------------------
1232
+
1233
+ /** Observer barrel export. */
1234
+ export const Version = {
1235
+ fallback: ANTIGRAVITY_VERSION_FALLBACK,
1236
+ minSupported: ANTIGRAVITY_VERSION_MIN_SUPPORTED,
1237
+ blocked: ANTIGRAVITY_BLOCKED_VERSIONS,
1238
+ remoteUrls: ANTIGRAVITY_VERSION_REMOTE_URLS,
1239
+ timeoutMs: VERSION_FETCH_TIMEOUT_MS,
1240
+ memoryTtlMs: VERSION_MEMORY_TTL_MS,
1241
+ fileTtlMs: VERSION_FILE_TTL_MS,
1242
+ memoryCache: versionMemoryCache,
1243
+ isValidSemver,
1244
+ cleanSemver,
1245
+ compareSemver,
1246
+ shouldUpdate,
1247
+ isBlockedVersion,
1248
+ isSupportedVersion,
1249
+ parseVersionFromPayload,
1250
+ fetchLatestVersion,
1251
+ getVersion,
1252
+ getVersionSync,
1253
+ clearVersionCache,
1254
+ clearVersionCacheSync,
1255
+ getAntigravityUserAgent,
1256
+ getDynamicUserAgentVersioned,
1257
+ } as const;
1258
+
1259
+ /** Default export - full merged module barrel (legacy + observer). */
1260
+ const versionModule = {
1261
+ // Legacy surface
1262
+ REMOTE_MANIFEST_URLS,
1263
+ CHANGELOG_URLS,
1264
+ FALLBACK_VERSION,
1265
+ FALLBACK_FULL_VERSION,
1266
+ FALLBACK_ALTERNATIVE_VERSION,
1267
+ FALLBACK_ALTERNATIVE_FULL,
1268
+ VERSION_CACHE_FILE,
1269
+ VERSION_CACHE_TTL_MS,
1270
+ FETCH_TIMEOUT_MS,
1271
+ ANTIGRAVITY_AUTH_CONSTANTS,
1272
+ fetchAntigravityVersion,
1273
+ getVersion,
1274
+ getVersionInfo,
1275
+ getVersionSync,
1276
+ getVersionInfoSync,
1277
+ parseAntigravityVersion,
1278
+ fetchWithTimeout,
1279
+ readCacheFile,
1280
+ readCacheFileSync,
1281
+ writeCacheFile,
1282
+ invalidateVersionCache,
1283
+ invalidateMemoryCache,
1284
+ getVersionedUserAgent,
1285
+ isNewerVersion,
1286
+ // Observer surface
1287
+ ANTIGRAVITY_VERSION_FALLBACK,
1288
+ ANTIGRAVITY_VERSION_MIN_SUPPORTED,
1289
+ ANTIGRAVITY_BLOCKED_VERSIONS,
1290
+ ANTIGRAVITY_VERSION_REMOTE_URLS,
1291
+ VERSION_FETCH_TIMEOUT_MS,
1292
+ VERSION_MEMORY_TTL_MS,
1293
+ VERSION_FILE_TTL_MS,
1294
+ versionMemoryCache,
1295
+ isValidSemver,
1296
+ cleanSemver,
1297
+ compareSemver,
1298
+ shouldUpdate,
1299
+ isBlockedVersion,
1300
+ isSupportedVersion,
1301
+ parseVersionFromPayload,
1302
+ fetchLatestVersion,
1303
+ clearVersionCache,
1304
+ clearVersionCacheSync,
1305
+ getAntigravityUserAgent,
1306
+ getDynamicUserAgentVersioned,
1307
+ Version,
1308
+ };
1309
+
1310
+ export default versionModule;